ReferenceAPISkills API

Updated 4 Nov 2026View as Markdown

Skills API

The server-to-server routes that keep an environment's Skills in step with the ones your system holds: endpoints, request and response fields, going live, listing, deleting, limits, errors and what version 1 promises.

Your system lists an environment's Skills, pushes the ones it keeps, reads them back and deletes the ones it no longer has, with an org API key. No person and no AI model takes part, and nothing needs doing in the Studio after the key is made.

Before you start#

KeyCreated in Settings → API keys, in the same environment as envSlug. Choose Skills only under Access: that key lists, reads, pushes and deletes Skills and reaches nothing else. A Full access key works too
HeaderThe key in x-api-key
CallersServers only. Keep the key out of a browser
Base pathhttps://YOUR-MINDSET-HOST/api/v1/orgs/{orgSlug}/envs/{envSlug}

Endpoints#

Method and pathWhat it doesScope
GET /skillsEvery Skill in the environment, in byte order of the handle, a page at a timeskills:read
PUT /skills/{handle}Makes the Skill at this handle equal the body, then applies liveskills:write
DELETE /skills/{handle}Deletes the Skill, with every versionskills:write
GET /skills/{handle}The Skill and its newest version, in fullskills:read
GET /skills/{handle}/versionsIts versions, newest first, a page at a timeskills:read
GET /skills/{handle}/versions/{version}One version, in fullskills:read

A handle is 1 to 64 lowercase letters, digits and hyphens, with no hyphen at either end and no two in a row: invoice-exceptions. Every route names the Skill by its handle.

Keep an environment in step#

A system that holds the Skills an environment should have makes three kinds of call:

  1. GET /skills, following nextCursor to the last page, for the handles the environment has now.
  2. PUT /skills/{handle} for each Skill the system holds, with live as it should be. A Skill that hasn't changed answers unchanged and adds nothing, so pushing every one on every run is safe.
  3. DELETE /skills/{handle} for each handle the environment has that the system doesn't.

From a SKILL.md folder#

A Skill in the Agent Skills format is a folder: a SKILL.md that opens with frontmatter between two --- lines, and other files beside it.

In the folderIn the push
The frontmatter's nameThe handle, in the path
The frontmatter's description, license, compatibility, metadata and allowed-toolsdescription, license, compatibility, metadata and allowedTools
Everything after the line that closes the frontmatter, the blank line that usually follows it includedinstructions, exactly as it is
Every other file, by its path from the folderfiles, each path to its text
Nothingname, a display name you choose, such as the first heading. Agents see it beside the description

For example, this folder:

text
invoice-exceptions/
  SKILL.md
  references/tolerances.md
  scripts/check_totals.py

with this SKILL.md:

markdown
---
name: invoice-exceptions
description: Use when an invoice does not match its purchase order.
license: Proprietary
metadata:
  owner: finance-ops
---

# Invoice exceptions

Flag any line over 5% of the agreed price.

is this push, to PUT /skills/invoice-exceptions. The instructions start with the blank line that follows the frontmatter, and the script is left out:

json
{
  "name": "Invoice exceptions",
  "description": "Use when an invoice does not match its purchase order.",
  "license": "Proprietary",
  "metadata": { "owner": "finance-ops" },
  "instructions": "\n# Invoice exceptions\n\nFlag any line over 5% of the agreed price.\n",
  "files": { "references/tolerances.md": "| Category | Tolerance |\n|---|---|\n| Goods | 2% |\n" },
  "live": true
}

Leave out files that are programs: anything under scripts/, and any file ending .py, .sh, .bash, .zsh, .js, .mjs, .cjs, .ts, .rb, .pl, .php, .ps1, .bat, .cmd or .exe. Mindset doesn't run them, and refuses a push that holds one with reason scripts_not_supported.

Push a Skill#

bash
curl -X PUT "https://YOUR-MINDSET-HOST/api/v1/orgs/acme/envs/production/skills/invoice-exceptions" \
  -H "x-api-key: $SKILLS_API_KEY" \
  -H "content-type: application/json" \
  -d '{
    "name": "Invoice exceptions",
    "description": "Use when an invoice does not match its purchase order.",
    "instructions": "# Invoice exceptions\n\nCompare the invoice lines with the purchase order. Flag any line over 5% of the agreed price.",
    "files": { "references/tolerances.md": "| Category | Tolerance |\n|---|---|\n| Freight | 10% |\n" },
    "live": true
  }'

The call is declarative: send the whole Skill as it should be.

Before the callWhat happensStatusoutcome
No Skill at the handleThe Skill is created at version 1201created
A Skill whose newest version differsOne version is added200updated
A Skill whose newest version is this documentNothing is added200unchanged

So a retried call never creates a second Skill or a second version. A different name renames the Skill and adds no version.

Request body

FieldTypeRequiredNotes
namestringyesThe display name, 1 to 255 characters. Every agent the Skill is attached to sees it beside the description, and the Studio shows it
descriptionstringyesWhen the Skill applies, up to 1,024 characters. An agent reads it to decide whether to load the Skill
instructionsstringyesThe body of the Skill's SKILL.md
filesobjectnoOther text files, by path relative to the Skill's root: { "references/tolerances.md": "…" }. No scripts, no binary files, no SKILL.md
licensestringnoUp to 500 characters
compatibilitystringnoUp to 500 characters
metadataobjectnoUp to 50 entries, each key up to 64 characters and each value up to 500
allowedToolsstringnoKept as written. It has no effect in Mindset
livebooleanyestrue makes this version the one agents load. false stores it and leaves the live version as it was

The body is strict: a field not in this table is refused, and the refusal names it. Instructions and files are stored byte for byte as sent, line endings and trailing spaces included.

Going live

live is required on every push. With true, the pushed version becomes the one agents load in the same step, and the version that was live stops being used. A Skill never has more than one live version, even when two pushes arrive at the same moment. With false, the version is stored and nothing agents load changes: a version that was live stays live, and a Skill with none stays unused. Push the same document again with true when it should go live.

Going live is refused when an agent the Skill is attached to already holds 50 published Skills, since this would make one more. An org admin attaches Skills to agents through the mindset-admin MCP server; this API doesn't.

Conditions

HeaderEffect
If-None-Match: *Create only. A 412 if a Skill already holds the handle
If-Match: "3"Store the push, or make the delete, only while version 3 is still the newest. A 412 if it has moved

Every read of a Skill and every push returns the newest version as the ETag, ready for If-Match. Writes to one Skill are taken one at a time, from this API and the mindset-admin MCP server alike.

Response

FieldWhat it is
outcomecreated, updated or unchanged
skillThe Skill: id, handle, name, status (published while a version is live, draft when none is), activeVersion (the live version, or null), createdBy, createdAt, updatedAt
versionThe version the pushed document is
liveWhether that version is the one agents load now
warningsAdvice the content earned, such as instructions longer than the Agent Skills specification recommends. The push still succeeded

Read a Skill#

GET /skills/{handle} and GET /skills/{handle}/versions/{version} return:

FieldWhat it is
skillThe Skill, as in a push's response
versionThe version's record: version, description, author and authorLabel (who wrote it), approvedBy, approvedByLabel and approvedAt (who last made it live, and when), isActive, totalBytes, createdAt
documentEverything the version holds, in the shape a push takes. Its name is the Skill's name now, since a name isn't versioned

Read document, change it, and push it back with live to make a change.

GET /skills/{handle}/versions returns versions, newest first, and nextCursor. Pass ?limit= for the page size (50 unless you ask, at most 200) and ?cursor= with the last page's nextCursor for the versions before it. nextCursor is null on the last page.

List Skills#

GET /skills returns skills, in byte order of the handle (- before digits, digits before letters), and nextCursor. Each entry is the Skill, as in a push's response, with newestVersion: the newest version's number, live or not. Pass ?limit= for the page size (50 unless you ask, at most 200; a larger number reads 200 and the answer's limitClamped says so) and ?cursor= with the last page's nextCursor for the Skills after it. nextCursor is null on the last page. A cursor is opaque: pass it back as you got it.

A Skill created or deleted while you page moves no other Skill between pages. A deleted Skill isn't listed, and neither is another environment's.

Delete a Skill#

bash
curl -X DELETE "https://YOUR-MINDSET-HOST/api/v1/orgs/acme/envs/production/skills/invoice-exceptions" \
  -H "x-api-key: $SKILLS_API_KEY"

A 200 returns deleted: true and skill, the Skill as it was. It is gone with every version: agents stop loading it, and it is no longer read or listed. Its handle is free, so a later push to it creates a new Skill at version 1. A delete is recorded in the org's audit trail, naming the key, and isn't undone through this API.

When agents hold the Skill, the first delete removes nothing. It answers 409 has_dependents, with the agents in context.dependents and a context.fingerprint for that list. Show the list to whoever decides, and to delete anyway, repeat the call with ?confirmDependents= and the fingerprint. If the agents holding it have changed since, the answer is 409 dependents_changed with the current list and a new fingerprint. A Skill no agent holds is deleted on the first call.

With If-Match: "3", the Skill is deleted only while version 3 is still its newest; if a newer version has been pushed since you read it, the answer is 412 precondition_failed and nothing is deleted.

A delete where there is no Skill, a retried delete that already succeeded among them, answers 404.

Limits#

WhatLimit
One version, instructions and files together, as stored4 MiB (4,194,304 bytes)
The request body16 MiB (16,777,216 bytes), so a Skill at its limit fits once escaped as JSON
Files in one version10,000
A file's path512 characters

A Skill over a limit is refused naming the limit and its size. Nothing is stored.

Errors#

Every error body is JSON with a code, a message, and often a context. An invalid Skill is refused once, with every problem in context.problems, each with its field and reason (up to 20, and context.problemsTotal when there are more).

What happenedStatuscode
A body that isn't JSON, misses a field, carries one this API doesn't define, or breaks a rule, including text that can't be stored as sent (a NUL character, or half of an emoji); or a limit, cursor or confirmDependents that isn't valid, or a query parameter the route doesn't define (named in the message)400validation_error
A handle that isn't a valid handle400invalid_handle
No key in x-api-key401unauthorized
A key that is unknown, revoked or expired401invalid_api_key
A push or delete with a key that can read Skills but not write them403unauthorized
A suspended org403org_suspended
No such Skill or version404not_found
The org or environment in the path isn't the key's own, or the key holds no Skills scope404not_found
Another change to the same Skill, made at the same moment, got there first. Read it again and retry409version_conflict
A delete of a Skill agents hold, without confirmDependents409has_dependents
A delete whose confirmDependents no longer matches the agents holding the Skill409dependents_changed
An If-None-Match or If-Match condition that didn't hold412precondition_failed
An If-Match that names no version, or If-None-Match on a delete400validation_error
A body over 16 MiB413payload_too_large

The 404s for a wrong org, environment or key look the same on purpose, so nobody holding a key can learn which orgs or environments exist.

What version 1 promises#

Within /api/v1, changes only add: new routes, new optional request fields, new response fields and new error codes. Ignore response fields you don't know, and treat an unknown code by its status. Nothing here is removed, renamed or narrowed within v1. A change that would break a caller ships as /api/v2 beside /api/v1, which keeps answering through a declared deprecation window and says so in Deprecation and Sunset headers.

Every push and every delete is recorded in the org's audit trail, naming the key, and every call is counted against the key that made it.

The OpenAPI 3.1 document for these routes, generated from the code that serves them, is customer-v1.json.