Workspace API
Workspace Skills
Personal and Workspace custom skills, immutable revisions, official installations, and the Skills API contract.
A skill is a portable instruction bundle a Spec run can load: a SKILL.md file plus optional inert reference files. This API is the public contract for discovery, reads, and lifecycle writes. The same routes serve the member web client, API-key clients, Spec, and the Developer MCP.
Personal and Workspace are audiences. Custom, official, and system are sources. They are not one enum. System skills stay hidden from public discovery; the reserved slugs create-skill and improve-skill cannot be claimed by a custom or official skill.
API keys with skills:read may read Workspace and Explore data. API keys with skills:write may perform Workspace lifecycle actions as a workspace automation principal. API keys never read or mutate Personal skills. Existing keys and MCP grants keep their frozen scopes; new keys receive both skill scopes, and existing credentials must be rotated or reauthorized.
Audiences
| Audience | Who sees it | Who can edit |
|---|---|---|
| Personal | Only the owning member. Invisible to API keys and to other members. | The owner. |
| Workspace | Every member of the workspace, subject to lifecycle and object auth. | The maintainer. Owners and admins may moderate, unpublish, delete, and transfer any Workspace skill. Creator attribution never changes. |
Any active member may create, edit, and publish a Workspace skill they maintain.
Lifecycle and revisions
| Lifecycle | Meaning |
|---|---|
draft | Being written. Never discoverable. The Personal owner, or a Workspace maintainer, owner, or admin, may read it. |
published | The current revision is live and discoverable. A Spec run may load it. |
unpublished | Withdrawn from discovery. Revisions survive and it can be published again. |
deleted | Terminal. Discovery stops and no bundle is returned. A deleted Personal override does not hide a Workspace skill. |
Published revisions are immutable. Update and publish send expectedDraftVersion. Unpublish, delete, transfer, and move send expectedVersion on the skill. Uninstall sends expectedVersion on the installation. Install has no expected version; it is ensure-state. A mismatch on a versioned write is 409 revision_conflict with the current version in allowlisted details.
list and get never return a deleted skill as a readable bundle. revisions lists published revisions only.
Run pins and official release resolution
Inside a Spec run, the first published read of a skill writes a run pin. Later published reads in that run return the same bytes even if a newer revision is published or an installation is re-pinned. The pin beats an explicit locator revisionId or releaseId and is applied silently; the returned detail names the revision served.
A pin bypasses two custom-get gates only: lifecycle !== 'published' and canReadPublished. A run that already held a pin can still read that revision after an unpublish. Soft delete and a skill-scope runtime block still refuse the next read. Official get has no post-pin lifecycle gate; uninstall mid-run does not block a pinned official read.
For an official skill the default release resolution order is:
- The run pin
- An explicit
locator.releaseId - The requesting actor’s installation release (Personal beats Workspace)
- Source latest
An official locator alone does not identify a bundle. Two members of one workspace can receive different bytes for the same official locator when one holds a personal installation at an older release. Production currently wires a not_populated official source: Explore is empty, and official get / install / official fork fail closed until a catalog is supplied.
Overrides
An override is a precedence mechanism, not a prohibition.
findapplies it. A visible override suppresses its target in the same L1 result set, for every actor who can see the override, at any audience.listreturns both the override and its target so you can inspect or uninstall the overridden skill.- An explicit
getof the overridden skill by locator is served.
The sanctioned shapes are Personal over Workspace-or-official, and Workspace over official. A Workspace skill cannot override a custom skill (400 invalid_input / override_audience). Draft, unpublished, deleted, and blocked overrides suppress nothing.
Runtime blocks
Prohibition is what skill-scope runtime blocks are for. A blocked custom or official skill disappears from find and list, and get / get-file fail closed with 403 disabled / runtime_block. Official fork and install apply the same key before content can be copied. uninstall is exempt so a block cannot strand an existing installation.
A skill_source block on custom or official degrades that source from L1 rather than raising.
Plan, scopes, and credits
Every Skills route requires the Pro plan for member and API-key principals. The gate runs after the workspace resolves and before the skill service. A non-Pro workspace receives 403 forbidden with the default Workspace API wording.
The verified Spec surface is exempt on the four allowlisted reads (find, list, get, get-file) so a free-plan Spec run can still load skills. revisions and every write, single and batch, stay Pro-gated for that same caller.
| Surface | Auth | Credits |
|---|---|---|
find, list, get, get-file | skills:read (API key or member JWT). Spec run tokens on the four reads. | 0 |
revisions | skills:read | 0 |
Create, update, publish, unpublish, delete, transfer, move, fork, install, uninstall (and each /batch) | skills:write | 0 |
Reads fail closed when skill loading is off (state: 'disabled' on find / list). Writes fail closed when skill writes are off.
Idempotency and receipts
Every write carries an idempotency key. Single routes require the Idempotency-Key header (1-200 characters). Each batch item carries idempotencyKey. MCP tools expose the same field. The store keeps a request digest and a bounded metadata receipt, never a second copy of skill content.
Every write returns a content-free receipt:
{ "skillId": "8a1f3c2e-0001-4b7d-9e5a-000000000010", "installationId": null, "draftVersion": 1, "publishedRevisionId": null, "lifecycle": "draft", "changed": true}Errors
Skills errors use a dedicated map. They are not the Data API errors page.
{ "error": { "code": "revision_conflict", "message": "skill revision_conflict", "reason": "stale_draft_version", "details": { "currentDraftVersion": 4 } }}| Service code | HTTP |
|---|---|
invalid_input | 400 |
validation, limit_exceeded | 422 |
not_found, official_source_not_populated | 404 |
forbidden, disabled | 403 |
revision_conflict, idempotency_conflict, slug_conflict, lifecycle_conflict | 409 |
official_source_unavailable | 503 |
internal_error | 500 |
details is optional and allowlisted to exactly these keys: path, currentVersion, currentDraftVersion, baseRevisionId, rootSha256, bundleSha256. Batch per-item errors carry the same four fields (code, message, reason, details). Envelope problems (empty batch, more than 100 items, malformed body) stay request-validation 400.