Skip to content

Workspace API

Manage Skills

Create, edit, publish, and reassign a skill with POST /workspace/skills/create, update, publish, unpublish, delete, transfer, move, and fork.

Every write below costs 0 credits, requires skills:write, and requires the Idempotency-Key header (1-200 characters). Pass workspace_id as a query parameter when authenticating with a user JWT; API keys infer it. Each route also has a batch sibling at POST /workspace/skills/<verb>/batch, accepting 1-100 items with a per-item idempotencyKey — see Batch Writes.

Every write returns a content-free receipt:

SkillWriteReceipt
{
"skillId": "8a1f3c2e-0001-4b7d-9e5a-000000000010",
"installationId": null,
"draftVersion": 1,
"publishedRevisionId": null,
"lifecycle": "draft",
"changed": true
}

Create a Skill

Endpoint
POST https://api.parcelengineering.com/api/v1/workspace/skills/create

Creates a Personal or Workspace skill from a portable bundle. The new skill starts as a draft; publish it (below) to make it discoverable. A Workspace skill may only override an official skill. The reserved slugs create-skill and improve-skill cannot be claimed. API keys cannot create Personal skills.

Request body

FieldTypeRequiredDescription
audiencepersonal | workspaceYesPersonal keeps it to you; Workspace shares it.
bundleobjectYes{ files: [{ path, mediaType, content }] }. mediaType is text/markdown, text/plain, or application/json.
overrideobjectNo{ kind: "custom", skillId } or { kind: "official", officialSkillId }. Personal may use either; Workspace may use official only.
authoringConversationIdstringNoSpec conversation this skill was authored in.

V1 files are bounded UTF-8 Markdown, plain text, or inert JSON. Matching .md / .markdown, .txt, and .json paths are accepted. Scripts, binaries, hooks, symlinks, executable manifests, and path traversal are refused. Fenced code blocks are kept as prose; Spec reads them and never runs them.

Example

Create a Workspace draft
curl -X POST https://api.parcelengineering.com/api/v1/workspace/skills/create \
-H "Authorization: Bearer pcl_your_api_key" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: create-skill-001" \
-d '{
"audience": "workspace",
"bundle": {
"files": [
{
"path": "SKILL.md",
"mediaType": "text/markdown",
"content": "<omitted>"
}
]
}
}'

Update a Skill Draft

Endpoint
POST https://api.parcelengineering.com/api/v1/workspace/skills/update

Replaces or restores the draft of a skill you may edit. Provide bundle to replace it, or restoreRevisionId to roll the draft back to a published revision. Update never publishes.

Request body

FieldTypeRequiredDescription
skillIdstringYesThe skill to edit.
expectedDraftVersionintegerYesDraft version you last read. A mismatch is 409 revision_conflict / stale_draft_version with details.currentDraftVersion.
bundleobjectExactly one of bundle or restoreRevisionIdReplacement portable bundle.
restoreRevisionIdstringExactly one of bundle or restoreRevisionIdRestore the draft from this published revision instead.

Sending both or neither is 400 invalid_input / update_source.

Example

Replace a draft
curl -X POST https://api.parcelengineering.com/api/v1/workspace/skills/update \
-H "Authorization: Bearer pcl_your_api_key" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: update-skill-001" \
-d '{
"skillId": "8a1f3c2e-0001-4b7d-9e5a-000000000010",
"expectedDraftVersion": 1,
"bundle": {
"files": [
{
"path": "SKILL.md",
"mediaType": "text/markdown",
"content": "<omitted>"
}
]
}
}'

Publish a Skill

Endpoint
POST https://api.parcelengineering.com/api/v1/workspace/skills/publish

Publishes the current draft as the next immutable revision, making the skill discoverable.

Request body

FieldTypeRequiredDescription
skillIdstringYesThe skill to publish.
expectedDraftVersionintegerYesDraft version you last read. A mismatch is 409 revision_conflict / stale_draft_version.

Example

Publish a draft
curl -X POST https://api.parcelengineering.com/api/v1/workspace/skills/publish \
-H "Authorization: Bearer pcl_your_api_key" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: publish-skill-001" \
-d '{
"skillId": "8a1f3c2e-0001-4b7d-9e5a-000000000010",
"expectedDraftVersion": 2
}'

Unpublish a Skill

Endpoint
POST https://api.parcelengineering.com/api/v1/workspace/skills/unpublish

Removes a skill from future discovery without deleting it or its revisions. A Spec run that already holds a pin on the published revision can still read those bytes for the rest of the run.

Request body

FieldTypeRequiredDescription
skillIdstringYesThe skill to unpublish.
expectedVersionintegerYesIdentity version you last read. A mismatch is 409 revision_conflict / stale_version with details.currentVersion.

Example

Unpublish a skill
curl -X POST https://api.parcelengineering.com/api/v1/workspace/skills/unpublish \
-H "Authorization: Bearer pcl_your_api_key" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: unpublish-skill-001" \
-d '{
"skillId": "8a1f3c2e-0001-4b7d-9e5a-000000000010",
"expectedVersion": 1
}'

Delete a Skill

Endpoint
POST https://api.parcelengineering.com/api/v1/workspace/skills/delete

Marks a skill deleted. Discovery stops and no bundle is returned afterwards. Deleted is terminal for every lifecycle mutation. A deleted Personal override does not hide a Workspace skill.

Request body

FieldTypeRequiredDescription
skillIdstringYesThe skill to delete.
expectedVersionintegerYesIdentity version you last read. A mismatch is 409 revision_conflict / stale_version.

Example

Delete a skill
curl -X POST https://api.parcelengineering.com/api/v1/workspace/skills/delete \
-H "Authorization: Bearer pcl_your_api_key" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: delete-skill-001" \
-d '{
"skillId": "8a1f3c2e-0001-4b7d-9e5a-000000000010",
"expectedVersion": 2
}'

Transfer a Skill

Endpoint
POST https://api.parcelengineering.com/api/v1/workspace/skills/transfer

Transfers maintainership of a Workspace skill to another current member. Owners and admins only. Creator attribution never changes. Personal skills cannot be transferred (403 forbidden / transfer_personal). Audience and override columns do not change.

Request body

FieldTypeRequiredDescription
skillIdstringYesThe Workspace skill to hand over.
expectedVersionintegerYesIdentity version you last read. A mismatch is 409 revision_conflict / stale_version.
newMaintainerIdstringYesMember who becomes the maintainer. Must be a current workspace member.

Example

Transfer a Workspace skill
curl -X POST https://api.parcelengineering.com/api/v1/workspace/skills/transfer \
-H "Authorization: Bearer pcl_your_api_key" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: transfer-skill-001" \
-d '{
"skillId": "8a1f3c2e-0001-4b7d-9e5a-000000000010",
"expectedVersion": 1,
"newMaintainerId": "8a1f3c2e-0001-4b7d-9e5a-000000000014"
}'

Move a Skill

Endpoint
POST https://api.parcelengineering.com/api/v1/workspace/skills/move

Moves a custom skill you may edit between the Personal and Workspace audiences. The mover becomes the owner of a Personal skill or the maintainer of a Workspace skill. Creator attribution never changes. Revisions, the draft and the identity version carry over; conversations already pinned to a revision do not move.

Requires the same authority as editing the draft: the owner of a Personal skill, or the maintainer, an owner or an admin of a Workspace skill. API keys may only move into Workspace (403 forbidden / personal_denied). Moving onto a name that already exists in the target audience is 409 slug_conflict / slug_taken. Moving to the audience the skill already has returns changed: false.

Request body

FieldTypeRequiredDescription
skillIdstringYesThe custom skill to move.
expectedVersionintegerYesIdentity version you last read. A mismatch is 409 revision_conflict / stale_version.
audience"personal" | "workspace"YesWhere the skill should live.

Example

Share a Personal skill with the workspace
curl -X POST https://api.parcelengineering.com/api/v1/workspace/skills/move \
-H "Authorization: Bearer pcl_your_api_key" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: move-skill-001" \
-d '{
"skillId": "8a1f3c2e-0001-4b7d-9e5a-000000000010",
"expectedVersion": 1,
"audience": "workspace"
}'

Fork a Skill

Endpoint
POST https://api.parcelengineering.com/api/v1/workspace/skills/fork

Forks a skill you can read into a new Personal or Workspace draft you own. A Workspace fork may only override an official skill. Run pins are not consulted; fork copies the locator you send.

Request body

FieldTypeRequiredDescription
sourceobjectYesA custom or official locator.
audiencepersonal | workspaceYesPersonal keeps the fork to you; Workspace shares it.
overrideobjectNoSame shapes as create. Workspace may name an official skill only.
namestringNoNew frontmatter name for the copy, so it can live beside its source. Validated like any skill name; omit to keep the source name.

Custom locator rules:

source fieldsWhat is copied
Neither revisionId nor viewThe published tip, with no authoring session.
revisionId setThat revision, under published-read auth. A revision belonging to another skill is 404 not_found / revision_not_found.
view: "draft"The draft, even when a published revision exists. Records no fork source revision.
Both revisionId and view: "draft"400 invalid_input / revision_and_draft_view.

Every path that copies draft content uses the same draft authorization as get: object auth on web, API, and MCP; a Spec run token additionally needs a matching authoring session (403 forbidden / draft_preview_denied when it does not). Official fork currently returns 404 official_source_not_populated. A visible-but-not-installable official release is 403 forbidden / official_not_installable.

Example

Fork a published custom skill
curl -X POST https://api.parcelengineering.com/api/v1/workspace/skills/fork \
-H "Authorization: Bearer pcl_your_api_key" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: fork-skill-001" \
-d '{
"source": {
"source": "custom",
"skillId": "8a1f3c2e-0001-4b7d-9e5a-000000000010"
},
"audience": "personal"
}'

Errors

Skills errors use a dedicated map. They are not the Data API errors page.

Error envelope
{
"error": {
"code": "revision_conflict",
"message": "skill revision_conflict",
"reason": "stale_draft_version",
"details": { "currentDraftVersion": 4 }
}
}
Service codeHTTP
invalid_input400
validation, limit_exceeded422
not_found, official_source_not_populated404
forbidden, disabled403
revision_conflict, idempotency_conflict, slug_conflict, lifecycle_conflict409
official_source_unavailable503
internal_error500

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.