Skip to content

API reference

Stable skills, immutable revisions, visibility, latest resolution, and asynchronous generation.

Paths below are on the cassette’s own listener. Through Tapes, replace /api/skills with /v1/cassettes/skills.

A skill is a stable organization-local identity with a unique normalized slug. It may temporarily contain no revisions. A revision is a complete immutable snapshot with:

  • globally unique id;
  • per-skill immutable sequenceNumber;
  • v1 version string derived from that sequence;
  • creator and optional base/source revision UUIDs;
  • complete content, hash, provenance, optional change note, and creation time.

Revision content never changes. isPublic and the skill’s optional explicit latestRevisionId are mutable metadata. Public means visible to members of the same organization; private means creator-only.

Every foreign relationship uses revision UUID. Version and sequence are display and ordering fields, not identity.

Route Purpose
POST /api/skills Create or resolve the hidden stable identity for a normalized slug
GET /api/skills Paginated list with one effective entry per accessible skill
GET /api/skills/{skillId} Read identity, effective public revision, and viewer-private continuation metadata
GET /api/skills/{skillId}/skill.md Download the effective public revision as SKILL.md
POST /api/skills/{skillId}/revisions Append a complete immutable private revision
GET /api/skills/{skillId}/revisions List public plus viewer-private revision history
GET /api/skills/{skillId}/revisions/{revisionId} Read one exact accessible revision
PUT /api/skills/{skillId}/revisions/{revisionId}/visibility Make a revision public or private without changing content
PUT /api/skills/{skillId}/latest Set or move the explicit latest pointer
DELETE /api/skills/{skillId}/latest Clear the explicit latest pointer
POST /api/skills/{skillId}/generations Durably queue independent asynchronous generation
GET /api/skills/{skillId}/generations List accessible generation history for a skill
GET /api/skills/{skillId}/generations/{generationId} Read persisted progress and artifacts
GET /api/skills/generations/{generationId} Resolve generation directly by public ID
POST /api/skills/{skillId}/generations/{generationId}/cancellations Cooperatively cancel and fence a generation

Duplicating a revision into another skill is not a separate route: resolve the target slug with POST /api/skills, read the source revision, then append its snapshot to the target with sourceRevisionId naming that source. SKILL.md is served only for a skill’s effective public revision; an exact revision’s content is read as JSON.

There is no mutable draft, autosave/checkpoint, publication-copy, generation resolution, revision deletion, synchronous generation, exact-revision Markdown, dedicated duplicate, or direct content-update route. V1 also exposes no arbitrary revision-tag operations; existing labels are a separate namespace.

The tenant-local gateway establishes organization membership and supplies trusted x-paper-auth-subject. Creator identity is never accepted in request JSON.

Storage applies access before loading content:

revision.isPublic OR revision.creatorSubject == callerSubject

Public is organization-visible, not internet-wide. Any organization member may append from an accessible revision and may change public/latest metadata in v1. A guessed private revision ID behaves as not found.

Evaluation history follows its exact target revision’s current visibility. Generation history is creator-only until it has a result revision; when that revision is public, bounded candidates/ranking/provenance become organization- visible with it.

Create or resolve a stable identity:

{
"slug": "diagnose-flaky-tests"
}

The slug is normalized and organization-unique. A collision returns the existing skill ID but no inaccessible private content. A zero-revision identity is hidden from ordinary listing.

Unversioned reads choose:

explicit public latest revision
else highest-sequence public revision
else no organization-visible default

GET /api/skills returns one entry per skill, never one per public revision. A creator also sees private-only skills using their newest private revision. When a public default exists, it remains the card/detail default even if the viewer has newer private work; newestPrivateRevision and hasNewerPrivateRevision support Continue your private revision.

Only equality with stored latestRevisionId sets isLatest: true. The automatic newest-public fallback does not receive an inferred Latest marker.

Listing keeps the existing bounded keyset/search/filter shape:

Parameter Meaning
limit Page size; bounded by the server maximum.
cursor Opaque keyset cursor from nextCursor. A cursor records the sort it was cut under and is rejected with 400 invalid_request when replayed under another sort; cursors issued by replicas that recorded no sort are accepted under either, so paging keeps working across a rolling deployment.
q Search accessible effective name, description, and labels.
scope all, mine, or team under creator/public visibility.
sort downloads or the default effective-update order.
session_id Return accessible skills attributed to a source session.

Configured external attachment filters remain fail-closed after arming.

{
"basedOnRevisionId": "revision-uuid-or-null",
"snapshot": {
"name": "Diagnose flaky tests",
"description": "Use when a test fails intermittently.",
"type": "workflow",
"tags": ["testing"],
"content": "# Diagnose flaky tests\n...",
"isAiGenerated": false,
"sourceSessionIds": []
},
"changeNote": "Add a reproducibility check",
"idempotencyKey": "editor-save-018"
}

The server assigns revision UUID, sequence, version, creator, timestamps, and private visibility. The snapshot carries no slug: slug is stable skill identity metadata resolved once by POST /api/skills. basedOnRevisionId may name any accessible revision in the same skill. Concurrent saves from one base all succeed with distinct sequences. The original remains unchanged.

sourceRevisionId is used instead of basedOnRevisionId for a cross-skill duplicate. The server does not copy anything: the client supplies the complete snapshot exactly as for any append (typically the snapshot it just read from the source revision), sourceRevisionId must name a revision of another skill that is accessible to the caller, and the new private revision records that source as lineage with origin duplicate. Repeating an idempotency key returns the retained append.

{ "isPublic": true }

PUT /visibility updates only visibility/audit metadata. Repeating the requested state is successful. Making the explicitly latest revision private returns a conflict until latest is moved or cleared.

Normal user APIs do not delete revisions. Making a revision private hides it from other organization members and hides its linked evaluation/generation history.

Set or move:

{ "revisionId": "revision-uuid" }

The target must be a public revision of the same skill. The pointer remains pinned when newer revisions become public. DELETE /latest clears the override and restores automatic newest-public resolution. Setting or clearing does not change content or visibility.

{
"baseRevisionId": "revision-uuid-or-null",
"input": {
"name": "Diagnose flaky tests",
"description": "Turn session evidence into a reusable workflow.",
"type": "workflow",
"tags": ["testing"]
},
"authorContext": "Prefer reproducible evidence.",
"selectedSessionIds": ["session-a", "session-b"]
}

A successful create returns 202 only after inputs and the immutable skills-cassette-owned ranking rubric are durable. Multiple generations for one skill/base may run concurrently. Statuses are:

queued -> generating_candidates -> evaluating_candidates -> synthesizing
-> completed | failed | canceled

There is no awaiting_input or resolution. Each selected session produces one independent candidate inference; context-only generation produces one candidate without a transcript. Partial source failures retain bounded diagnostics while healthy candidates continue.

Every viable candidate and optional synthesis is judged under the same profile, version, weighted criteria, author context, and explicit evidence ordering by skills-evaluator. Skills-cassette deterministically ranks persisted results.

Successful completion appends exactly one new private revision and returns its resultRevisionId. It never makes that revision public or latest. Cancellation retains artifacts and fences late worker writes.

A deployment may close generation admission with CASSETTE_GENERATION_ENABLED=false. Creating a generation then returns 403 with code generation_disabled, decided before anything is written, so nothing is queued and then refused. Nothing else changes: listing and reading skills, revisions and generation history, resolving a skill identity, appending a revision, visibility and latest all behave exactly as before.

Work already in the durable queue is finished, not dropped — including a row whose lease expired because its worker died. Only a new attempt is new admission: a row whose attempt fails while admission is closed is not requeued and goes terminal instead.

Two evaluator contracts cross the boundary with skills-evaluator, in opposite directions.

Stateless candidate judgment is a call from this cassette to /v1/cassettes/skills-evaluator/candidate-evaluations. The request carries the complete rendered candidate (and optional baseline), the immutable profile/version/criteria snapshot stored on the generation, author context, and explicit evidence session order; the creator subject travels only in the trusted header. The evaluator echoes the request ref verbatim. Its ref record also carries revision and revision_sha256, which candidate work never sets; the adapter accepts them as bounded opaque strings and still requires source and id to match. Nothing about a candidate evaluation is persisted by skills-evaluator.

Durable user-requested evaluation is a call to skills-evaluator naming skill_id and an exact skill_revision_id. Skills-evaluator resolves that revision through GET /api/skills/{skillId}/revisions/{revisionId} (via the Tapes prefix) with the forwarded viewer subject, judges that immutable snapshot whether private or public, and persists the revision UUID on the run. It never re-resolves latest: an explicit candidate whose content differs from the named revision is a conflict. Evaluation-run reads re-check the live revision the same way, so runs are creator-only while the revision is private and organization-visible while it is public.

Both contracts are verified against the real HTTP applications by make test-skills-evaluator-contract.

Lifecycle failures use a bounded stable envelope:

{
"error": {
"code": "revision_not_public",
"message": "Only a public revision can be marked latest.",
"resourceId": "revision-uuid"
}
}

Codes include revision_not_found, revision_private, revision_not_public, latest_revision_conflict, revision_sequence_conflict, invalid_generation_state, and generation_disabled. Inaccessible private content uses not-found semantics. Responses never include raw transcripts, provider/model errors, creator subjects, claim tokens, or lease state.

Console may present Save new revision, Make public, and Make latest together, but calls append → visibility → latest as separate operations. Make latest selects and locks Make public. If a later operation fails, earlier success remains committed and the client retries only the failed step.