API reference
Several endpoints. The skilvault client uses them for you; call them directly to build your own tooling.
Base URL and authentication
Every endpoint lives under your SkilVault host, written https://your-skilvault-host below. All but the installer need an API key (API keys). Send it either way:
| Method | How | Notes |
|---|---|---|
| Header | Authorization: Bearer svk_live_… | What the client uses, and what to use in your own scripts: headers do not end up in logs. |
| Query parameter | ?k=svk_live_… | Still accepted, for older clients. Avoid it: addresses are written to server and proxy logs. |
Responses are JSON (except the pack and the installer) and are never cacheable: Cache-Control: no-store.
GET /marketplace.json
Your personal manifest: your pinned skills, each with the checksum of its latest version and a download URL that already carries your key. This is what drives what your CLIs list, so it stays pinned-only even on plans that can read the whole library — see GET /api/v1/skills/{name}/source below to read an unpinned one.
{
"name": "skilvault",
"owner": { "name": "SkilVault", "url": "https://your-skilvault-host" },
"settings": { "suggestSkills": false, "shareLearnings": false, "reportUsage": true, "routeSkills": true },
"plugins": [
{
"name": "commit-lint",
"description": "Conventional-commit checks…",
"version": "1.0.0",
"keywords": ["git", "commits"],
"category": "Development",
"source": {
"source": "archive",
"url": "https://your-skilvault-host/api/v1/packs/commit-lint/download",
"sha256": "9f2b…",
"signature": "-----BEGIN SSH SIGNATURE-----…"
}
}
]
}pluginslists exactly your library, so a change in the dashboard shows here on the very next request.keywordsandcategoryappear only when the skill has them.settings.suggestSkillsmirrors Suggest skills for my projects. The client checks it before sending anything to/api/v1/suggest.settings.reportUsagemirrors Report which skills you use; the client sends uses to/api/v1/usageonly while it is on.settings.routeSkillsmirrors Suggest vault skills from your prompts, which runs on your machine only.settings.materializeSkillsis still sent for clients from before September 2026, which read it to keep full copies current. Current clients ignore it (Skills in your CLI).
GET /api/v1/packs/{name}/download
Streams the pack (a zip) for the skill whose name is {name}. Skills in your library can always be downloaded; on a plan with includesAllSkills (Pro, Unlimited), any published skill can be downloaded, not only pinned ones.
| Status | Meaning |
|---|---|
200 | The zip. Headers: Content-Type: application/zip, ETag: "<sha256>", X-SkilVault-Version: <version>, Cache-Control: private, no-store. |
401 | Unknown or revoked key. |
404 | no such pack — it does not exist, is unpublished, or you cannot read it on your plan. These are deliberately indistinguishable. |
429 | Hourly limit reached (Usage & rate limits). |
The ETag equals the sha256 in your manifest. Always verify it — see the example below.
source.signature is the operator's offline signature (ssh-keygen -Y sign, namespace skilvault-skill) over the textskilvault-skill-v1\n<name>\n<version>\n<sha256>\n. Verify it against keys you obtained out of band, never against keys the server sends.
GET /api/v1/skills/{name}/source
A one-skill manifest for a skill your plan can read but that is not pinned (ADR 0021) — how skilvault read reaches a skill outside your library on Pro and Unlimited. Send the key as above, in an Authorization: Bearer svk_live_… header.
The response is shaped exactly like /marketplace.json, with a single entry in plugins — the same source.sha256 and either a source.signature or a batch reference, plus the same trust, batches and learning-loop footer — so the client verifies it with the same signature, batch, trust and rollback checks as any pinned skill.
| Status | Meaning |
|---|---|
200 | The one-skill manifest, JSON, Cache-Control: no-store. |
401 | Unknown or revoked key. |
404 | no such skill — it does not exist, is unpublished, or you cannot read it on your plan (the free plan, or it is neither pinned nor readable). Deliberately the same 404 as the download route. |
429 | Rate limited like the manifest, at a light weight (Usage & rate limits). |
GET /api/v1/catalog/index
Every published skill, so the client matches requests on your machine (skilvault find and the prompt router, ADR 0021): the request itself is never sent. Same key as above. Add ?since=<cursor> with the cursor of your last answer to get only what changed since then (ADR 0023).
{
"readAll": true, // your plan reads skills you haven't pinned
"cursor": "2026-09-26T00:10:50.030Z",
"full": true, // false for an answer to ?since=
"skills": [
{ "name": "commit-lint", "version": "1.0.0", "description": "…", "keywords": ["git"], "category": "Development",
"vec": "…base64…", // 384 signed bytes (x·127): the description's vector, made by the server with the client's model
"examples": "…base64…" } // 384 signed bytes per example request, one after another; null when it has none
],
"removed": [] // with ?since=: skills no longer published
}The names and descriptions are the ones public on the catalog page; nothing in it is signed, so the client never hands an unpinned skill's description to an agent unasked. The response carries an ETag: send it back as If-None-Match and an unchanged catalog answers 304 with no body. 401 for a bad key, 429 at the light rate limit.
GET /api/v1/search
Ranks the whole published catalog against a query, mixing keyword and semantic matching. The client uses it only when nothing in its local copy of the catalog fits; a request that matches nothing is recorded as a skill people want (ADR 0007).
| Parameter | Required | Description |
|---|---|---|
q | yes | The search text. Missing or blank returns 400. |
limit | no | Number of results, 1–20. Default 8. |
{
"query": "review a pull request",
"results": [
{
"name": "code-review",
"description": "Review the changes since a fixed point…",
"version": "1.0.0",
"selected": true,
"source": { "source": "archive", "url": "https://…/download", "sha256": "…" }
},
{
"name": "commit-lint",
"description": "Conventional-commit checks…",
"version": "1.0.0",
"selected": false
}
]
}selected says whether the skill is in your library. Only selected results include a source — unselected ones are metadata only, with no download URL or checksum. The text of your search is recorded with your usage (see Security & privacy).
POST /api/v1/suggest
Which skills fit a project, given its stack keywords. The body is exactly { "terms": ["nextjs", "stripe"] }: up to 20 lower-case keywords (letters, digits, spaces and . + # -, at most 41 characters each). Anything else, including one bad term, returns 400, so nothing but keywords can be sent. A skill is suggested only when a keyword appears as a whole word in its name, keywords or description.
{
"terms": ["nextjs", "docker"],
"results": [
{ "name": "docker-troubleshooting", "description": "…", "version": "1.0.0", "selected": true,
"matchedTerms": ["docker"], "source": { "source": "archive", "url": "https://…/download", "sha256": "…" } }
]
}As with search, only selected skills include a source. The keywords are recorded with your usage, and a keyword nothing matches may be kept (without your name) as a request for a new skill.
POST /api/v1/preferences
Sets preferences for the key's account: any of suggestSkills, reportUsage and routeSkills, each true or false, for example { "suggestSkills": true }. Anything else is refused. At most 10 changes an hour. The installer calls it when you answer yes to “Suggest vault skills for the projects you work on?”.
POST /api/v1/usage
Records which skills the key's account used, sent by the client in the background (ADR 0018). The body is { "uses": [ { "skill", "sha256", "client", "at" } ] } with 1 to 50 uses: the skill's name, the checksum of the version used, which CLI used it (claude, codex, grok, agy or other) and when. Nothing else is accepted. Uses of skills the account cannot use, repeats, and uses older than a day are dropped and counted as ignored. When the account has turned off Report which skills you use, nothing is stored. At most 600 uses per key per hour. Answers 201 { "stored": n, "ignored": m }.
POST /api/v1/skills/{name}/feedback
Sends one confirmed rule about a skill your key can use, as { "text": "…", "version": "x.y.z" } (version optional). Only accepted when the account has turned on sharing; the text is one line of 10 to 300 characters and is refused if it looks like a secret or an instruction aimed at an AI. Sending the same rule again returns the same id. At most 20 per hour per account. The client's skilvault note share does this for you.
GET /api/v1/client/memory/{file}
The files skilvault memory install downloads (the memory package and the local model). Public; the client checks every file against a fingerprint built into it and refuses any mismatch.
GET /api/v1/client/install.sh
A public shell script that installs the client. No key is needed to fetch it. Send your key in an Authorization: Bearer svk_live_… header to have the installer also save it to your config; a key in the wrong format returns 400. Pipe it to sh, or read it first — see Connecting a machine.
Errors
Errors are JSON: {"error":"…"}.
| Status | Message | Cause |
|---|---|---|
400 | missing required query param: q / malformed API key | A bad request. |
401 | unknown or revoked API key | Missing, mistyped or revoked key. |
404 | no such pack | Not in your library, or unpublished. |
429 | rate limit exceeded — retry within the hour | Limit reached. Header Retry-After: 3600. |
Examples
curl -s -H "Authorization: Bearer $SV_KEY" https://your-skilvault-host/marketplace.json \
| python3 -c 'import json,sys; [print(p["name"], p["version"]) for p in json.load(sys.stdin)["plugins"]]'SHA=$(curl -s -H "Authorization: Bearer $SV_KEY" https://your-skilvault-host/marketplace.json \
| python3 -c 'import json,sys; print(next(p["source"]["sha256"] for p in json.load(sys.stdin)["plugins"] if p["name"]=="commit-lint"))')
curl -s -H "Authorization: Bearer $SV_KEY" -o commit-lint.zip \
https://your-skilvault-host/api/v1/packs/commit-lint/download
echo "$SHA commit-lint.zip" | shasum -a 256 -c -curl -s -G -H "Authorization: Bearer $SV_KEY" \
--data-urlencode "q=review a pull request" --data-urlencode "limit=5" \
https://your-skilvault-host/api/v1/search