Workspace API
Read a Skill
Fetch one skill, one of its files, or its revision history with POST /workspace/skills/get, /get-file, and /revisions.
All three routes below share one locator: { source: "custom", skillId, revisionId?, view? } or { source: "official", officialSkillId, releaseId? }. Each costs 0 credits and requires skills:read. Pass workspace_id as a query parameter when authenticating with a user JWT; API keys infer it.
The Workspace API requires the Pro plan. A request from a non-Pro workspace or a non-member receives 403 forbidden. get and get-file are exempt for a verified Spec run token; revisions is Pro-gated for every caller, including Spec.
view: "draft" is authorized by object auth on web, API, and MCP (Personal owner, or Workspace maintainer, owner, or admin). A Spec run token additionally needs a matching authoring session; without one the run is refused 403 forbidden / draft_preview_denied. Inside a Spec run, a run pin beats an explicit revisionId or releaseId — the first published read of a skill in a run writes the pin, and later published reads in that run return the same bytes even if a newer revision is published. For an official skill without a pin, resolution falls back to an explicit releaseId, then the actor’s installation release, then source latest. Production official reads currently return 404 official_source_not_populated.
Get a Skill
POST https://api.parcelengineering.com/api/v1/workspace/skills/getFetches one skill by locator, including instructions, metadata, and the list of files it carries. File bytes are not inlined; use get-file below for a path.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
locator | object | Yes | Identifies the skill and, optionally, which revision or view. |
Locator forms:
| Form | Fields |
|---|---|
| Custom | source: "custom", skillId, optional revisionId, optional view (published | draft) |
| Official | source: "official", officialSkillId, optional releaseId |
Omitted or published view never exposes a draft. When view is draft, the draft is served and revisionId is not consulted.
Example
curl -X POST https://api.parcelengineering.com/api/v1/workspace/skills/get \ -H "Authorization: Bearer pcl_your_api_key" \ -H "Content-Type: application/json" \ -d '{ "locator": { "source": "custom", "skillId": "8a1f3c2e-0001-4b7d-9e5a-000000000010" } }'const response = await fetch( 'https://api.parcelengineering.com/api/v1/workspace/skills/get', { method: 'POST', headers: { 'Authorization': 'Bearer pcl_your_api_key', 'Content-Type': 'application/json', }, body: JSON.stringify({ locator: { source: 'custom', skillId: '8a1f3c2e-0001-4b7d-9e5a-000000000010', }, }), });
const { data, metadata } = await response.json();Response
| Field | Type | Description |
|---|---|---|
locator | object | The locator that was served. |
name, description, slug | string | Content-free identity. |
audience | personal | workspace | Who the skill is for. |
source | custom | official | Authored here or installed. |
lifecycle | draft | published | unpublished | deleted | Current lifecycle. |
createdBy, maintainerId | string | null | Member ids. Creator never changes on transfer. |
installed | boolean | True when this row is an official installation. |
revision, sha256, updatedAt | string | Summary checksums and recency. |
hasUnpublishedChanges | boolean | True for a published custom skill whose saved draft differs from what is live. |
skillMd, instructions | string | Bundle text. Omitted from examples. |
license, compatibility | string | null | Optional frontmatter. |
metadata | object | null | Optional frontmatter map. |
files | array | { path, mediaType, bytes, sha256 } per file. No file contents. |
revisionId, bundleSha256 | string | The served revision. |
view | published | draft | Which bundle was served. |
draftVersion | integer | null | Draft optimistic-concurrency version. Null when this view has none. |
version | integer | null | Skill identity version. Null when this view has none. |
installationId | string | null | Active installation id, if any. |
installationVersion | integer | null | Installation optimistic-concurrency version, if any. |
{ "data": { "locator": { "source": "custom", "skillId": "8a1f3c2e-0001-4b7d-9e5a-000000000010" }, "name": "PDF helper", "description": "Fills and stamps PDF forms.", "audience": "workspace", "source": "custom", "lifecycle": "published", "slug": "pdf-helper", "createdBy": "8a1f3c2e-0001-4b7d-9e5a-000000000013", "maintainerId": "8a1f3c2e-0001-4b7d-9e5a-000000000013", "installed": false, "revision": "1", "sha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "updatedAt": "2026-09-01T12:00:00.000Z", "hasUnpublishedChanges": false, "skillMd": "<omitted>", "instructions": "<omitted>", "license": null, "compatibility": null, "metadata": null, "files": [ { "path": "SKILL.md", "mediaType": "text/markdown", "bytes": 128, "sha256": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" } ], "revisionId": "8a1f3c2e-0001-4b7d-9e5a-000000000011", "bundleSha256": "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc", "view": "published", "draftVersion": null, "version": 1, "installationId": null, "installationVersion": null }, "metadata": { "credits": 0 }}Get a Skill File
POST https://api.parcelengineering.com/api/v1/workspace/skills/get-fileFetches the contents of one file inside a skill, addressed by locator and path. Uses the same locator resolution, run pins, and draft authorization as get above.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
locator | object | Yes | Same custom and official forms as get. |
path | string | Yes | Path of the file inside the skill bundle. |
A missing path is 404 not_found / file_not_found.
Example
curl -X POST https://api.parcelengineering.com/api/v1/workspace/skills/get-file \ -H "Authorization: Bearer pcl_your_api_key" \ -H "Content-Type: application/json" \ -d '{ "locator": { "source": "custom", "skillId": "8a1f3c2e-0001-4b7d-9e5a-000000000010" }, "path": "SKILL.md" }'const response = await fetch( 'https://api.parcelengineering.com/api/v1/workspace/skills/get-file', { method: 'POST', headers: { 'Authorization': 'Bearer pcl_your_api_key', 'Content-Type': 'application/json', }, body: JSON.stringify({ locator: { source: 'custom', skillId: '8a1f3c2e-0001-4b7d-9e5a-000000000010', }, path: 'SKILL.md', }), });
const { data, metadata } = await response.json();Response
{ "data": { "path": "SKILL.md", "mediaType": "text/markdown", "content": "<omitted>", "bytes": 128, "sha256": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", "revisionId": "8a1f3c2e-0001-4b7d-9e5a-000000000011" }, "metadata": { "credits": 0 }}List Skill Revisions
POST https://api.parcelengineering.com/api/v1/workspace/skills/revisionsLists published revisions for a custom skill. This route is API-only: there is no Developer MCP tool for it. Revisions are immutable. nextCursor is always null; there is no cursor input. Unlike get and get-file, this read is Pro-gated for every caller, including a Spec run token.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
skillId | string | Yes | The custom skill whose published revisions to list. |
limit | integer 1..200 | No | Defaults to 50. |
Example
curl -X POST https://api.parcelengineering.com/api/v1/workspace/skills/revisions \ -H "Authorization: Bearer pcl_your_api_key" \ -H "Content-Type: application/json" \ -d '{ "skillId": "8a1f3c2e-0001-4b7d-9e5a-000000000010", "limit": 50 }'const response = await fetch( 'https://api.parcelengineering.com/api/v1/workspace/skills/revisions', { method: 'POST', headers: { 'Authorization': 'Bearer pcl_your_api_key', 'Content-Type': 'application/json', }, body: JSON.stringify({ skillId: '8a1f3c2e-0001-4b7d-9e5a-000000000010', limit: 50, }), });
const { data, metadata } = await response.json();Response
{ "data": { "items": [ { "revisionId": "8a1f3c2e-0001-4b7d-9e5a-000000000011", "revision": 1, "bundleSha256": "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc", "publishedAt": "2026-09-01T12:00:00.000Z", "publishedBy": "8a1f3c2e-0001-4b7d-9e5a-000000000013" } ], "nextCursor": null }, "metadata": { "credits": 0 }}