REST API

Everything the skillsvault CLI does, it does over this API — and so can you. Use it to publish skills from CI, mirror your registry, fetch and verify the signed policy bundle, or stream audit events into your own systems. All endpoints are JSON in / JSON out and authenticate with an agent key.

Base URL

The base URL is your organisation's skillsvault control-plane origin (for example https://skillsvault.io). Every path below is relative to it. The :orgSlug path segment is your organisation's slug.

Authentication

Send your agent key on every request (except GET /api/bundle/key, which is public). Either header works:

X-Skillsvault-Key: skillsvault_ak_…
Authorization: Bearer skillsvault_ak_…

A key is scoped to exactly one organisation. Create or revoke keys in Settings → Agent keys; see Install & connect for the full lifecycle.

  • 401 — the key is missing or unknown.
  • 403 — the key is valid but used against a different org than the one in the path.

Conventions

Requests and responses are JSON unless noted. Send Content-Type: application/json on writes. A minimal authenticated call looks like:

curl https://skillsvault.io/api/skills/acme \
  -H "X-Skillsvault-Key: skillsvault_ak_live_xxx"

Error responses carry a JSON body describing the problem; see Errors for the status codes you should handle.

GET /api/skills/:orgSlug

List the organisation's approved skills and their current state — the same manifest the CLI uses to decide what to install.

Auth: agent key.

Response

{
  "org": "acme",
  "skills": [
    {
      "name": "acme/hubspot-crm-entry",
      "version": "1.3.0",
      "status": "active",
      "compliance": "compliant",
      "content_hash": "sha256:77de…"
    }
  ]
}

status is one of active, deprecated, or banned; compliance is compliant or non_compliant.

curl https://skillsvault.io/api/skills/acme \
  -H "X-Skillsvault-Key: skillsvault_ak_live_xxx"

Status: 200 · 401 · 403.

GET /api/skills/:orgSlug/content

Fetch the full content of one skill's current version — its SKILL.md and every bundled file.

Auth: agent key.

Query

ParamRequiredDescription
skillyesThe namespaced skill name, e.g. acme/hubspot-crm-entry.

Response

{
  "name": "acme/hubspot-crm-entry",
  "version": "1.3.0",
  "status": "active",
  "skill_md": "---\nname: hubspot-crm-entry\n…",
  "files": {
    "scripts/run.py": "…",
    "references/fields.md": "…"
  }
}

files maps each relative path to its text content.

curl "https://skillsvault.io/api/skills/acme/content?skill=acme/hubspot-crm-entry" \
  -H "X-Skillsvault-Key: skillsvault_ak_live_xxx"

Status: 200 · 400 (missing ?skill) · 401 · 403 · 404 (skill not found).

POST /api/skills/:orgSlug/publish

Publish a skill folder as a new immutable version — the API behind skillsvault publish. See Authoring & publishing and the manifest reference.

Auth: agent key.

Body

FieldRequiredDescription
nameyesNamespaced namespace/skill, lowercase.
versionyesSemantic version, e.g. 1.4.0.
skill_mdyesThe full SKILL.md text.
descriptionnoDiscovery description (mirrors the manifest / frontmatter).
intentnoMachine intent (kebab-case).
targetsnoArray of systems/processes the skill touches.
filesnoMap of relative path → file content for bundled scripts/, references/, assets/.
manifestnoThe parsed skillsvault.json object.
{
  "name": "acme/hubspot-crm-entry",
  "version": "1.4.0",
  "description": "Create and update HubSpot contacts from a deal brief.",
  "intent": "crm-data-entry",
  "targets": ["hubspot"],
  "skill_md": "---\nname: hubspot-crm-entry\n…",
  "files": { "scripts/run.py": "…" },
  "manifest": { "skillsvault_schema": 1, "name": "acme/hubspot-crm-entry", "…": "…" }
}

Response

{
  "ok": true,
  "skill": "acme/hubspot-crm-entry",
  "version": "1.4.0",
  "content_hash": "sha256:9a1b…"
}
curl -X POST https://skillsvault.io/api/skills/acme/publish \
  -H "X-Skillsvault-Key: skillsvault_ak_live_xxx" \
  -H "Content-Type: application/json" \
  -d @publish.json

Status: 200 · 400 (bad JSON) · 401 · 403 · 422 (missing required field or invalid name).

GET /api/bundle/:orgSlug

Fetch the signed policy bundle — the snapshot of approved skills and active policies that the gate evaluates locally.

Auth: agent key.

Response — a signed envelope:

{
  "alg": "Ed25519",
  "payload": "{\"bundle_version\":1750848000, …}",
  "signature": "base64…"
}

payload is a JSON string: verify signature over its exact bytes using the org's public key, then parse it. The decoded payload contains:

FieldMeaning
bundle_versionMonotonic version of this bundle.
defaultsFallback actions (unmanaged, managed_missing_manifest) and staleness settings.
namespacesThe managed namespaces.
manifest_indexMap of skillcurrent_version, content_hash, status, targets.
policiesOrdered list of policy_id, name, matcher_type, match_value, action, reason.
curl https://skillsvault.io/api/bundle/acme \
  -H "X-Skillsvault-Key: skillsvault_ak_live_xxx"

Status: 200 · 401 · 403 · 404 (org not found).

GET /api/bundle/key

Return the organisation's Ed25519 public signing key, PEM-encoded, as text/plain. Use it to verify the signature on a bundle from GET /api/bundle/:orgSlug. This endpoint is public — no agent key required.

curl https://skillsvault.io/api/bundle/key
-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEA…
-----END PUBLIC KEY-----

Status: 200.

POST /api/audit/ingest

Append one or more audit events — the API the gate uses to record every decision. See Audit log.

Auth: agent key.

Body

{
  "events": [
    {
      "skill_name": "acme/teamleader-crm-entry",
      "skill_version": "2.0.0",
      "harness": "claude_code",
      "machine_name": "lab-mbp",
      "decision": "blocked",
      "policy_id": "pol_7731",
      "policy_name": "Teamleader retired",
      "reason": "Use acme/hubspot-crm-entry instead.",
      "occurred_at": "2026-06-25T10:03:12Z"
    }
  ]
}

decision is one of allowed, warned, or blocked. occurred_at, policy_id, policy_name, and reason are optional.

Response

{ "received": 1 }
curl -X POST https://skillsvault.io/api/audit/ingest \
  -H "X-Skillsvault-Key: skillsvault_ak_live_xxx" \
  -H "Content-Type: application/json" \
  -d @events.json

Status: 200 · 400 (bad JSON) · 401.

POST /api/devices/heartbeat

Report a machine's installed skill inventory and CLI version, and learn whether the CLI is out of date. Sent automatically by the CLI on connect and after skillsvault pull.

Auth: agent key.

Body

{
  "harness": "claude_code",
  "machine_name": "lab-mbp",
  "plugin_version": "0.1.0",
  "installed": [
    { "skill": "acme/hubspot-crm-entry", "version": "1.3.0", "status": "active" }
  ]
}

All fields are optional and default sensibly (harnessclaude_code, machine_nameunknown, installed[]).

Response

{
  "ok": true,
  "installed": 1,
  "outdated": false,
  "latest_version": "0.1.0",
  "release_url": null
}

Status: 200 · 400 (bad JSON) · 401.

CLI login (device flow)

Connecting a machine with skillsvault login uses a browser device-authorization flow: the CLI calls POST /api/cli/device/start to obtain a device_code and a short user_code, opens the verification page, then polls POST /api/cli/device/poll with the device_code until you approve it in the browser — at which point it receives a real agent key. Integrators normally just run skillsvault login (or skillsvault configure with a key) rather than calling these endpoints directly.

Errors

StatusMeaning
400Malformed request (bad JSON, or a missing required query parameter).
401Missing or unknown agent key.
403Valid key used against a different organisation than the path.
404Org, skill, or resource not found.
422Validation failed — a required field is missing or a name is malformed.