components:
schemas:
Discovery:
description: |-
Discovery is the document served at GET /v1/cassettes.
It publishes what each cassette *is* — never what it is configured to. Core
holds no configuration values—the deployment supplies them directly to the
cassette—so there is nothing here to leak.
properties:
cassettes:
items:
$ref: '#/components/schemas/DiscoveryEntry'
type: array
contract_version:
type: string
problems:
items:
$ref: '#/components/schemas/Rejection'
type: array
type: object
DiscoveryDepends:
description: DiscoveryDepends is a cassette's declared dependency on core.
properties:
core:
type: string
views:
items:
type: string
type: array
type: object
DiscoveryEntry:
description: |-
DiscoveryEntry describes one served cassette.
The OpenAPI document is referenced, not inlined. A single spec runs to tens
of kilobytes and clients poll discovery; inlining five of them turns every
client boot into a megabyte of mostly-unchanged bytes. The digest is enough
to know whether the fetch is worth making.
properties:
config:
items:
$ref: '#/components/schemas/DiscoverySetting'
type: array
depends:
$ref: '#/components/schemas/DiscoveryDepends'
description:
type: string
display_name:
type: string
manifest_digest:
type: string
name:
type: string
openapi_path:
type: string
openapi_status:
type: string
route_prefix:
type: string
tables:
items:
type: string
type: array
version:
type: string
type: object
DiscoverySetting:
description: DiscoverySetting is one configuration key as a schema, never as
a value.
properties:
default: {}
description:
type: string
key:
type: string
required:
type: boolean
secret:
type: boolean
type:
type: string
type: object
ErrorResponse:
description: ErrorResponse represents an error from the LLM API.
properties:
error:
type: string
type: object
MCPError:
description: MCPError is a JSON-RPC 2.0 error object.
properties:
code:
description: Code is the JSON-RPC error code.
example: -32600
format: int32
type: integer
message:
description: Message is a short description of the failure.
example: invalid request
type: string
type: object
MCPRequest:
description: MCPRequest is a JSON-RPC 2.0 request to the streamable MCP endpoint.
properties:
id:
description: ID correlates a response with this request. Absent on notifications.
example: "1"
type: string
jsonrpc:
description: JSONRPC is the protocol version, always "2.0".
example: "2.0"
type: string
method:
description: Method is the MCP method being invoked, such as tools/call.
example: tools/call
type: string
params:
additionalProperties: {}
description: Params are the method's arguments.
type: object
type: object
MCPResponse:
description: |-
MCPResponse is a JSON-RPC 2.0 response from the streamable MCP endpoint.
Exactly one of Result and Error is set, which is the JSON-RPC contract rather
than anything this server adds.
properties:
error:
$ref: '#/components/schemas/MCPError'
id:
description: ID is the id of the request this answers.
example: "1"
type: string
jsonrpc:
description: JSONRPC is the protocol version, always "2.0".
example: "2.0"
type: string
result:
additionalProperties: {}
description: Result is the method's return value on success.
type: object
type: object
MainUsage:
description: |-
MainUsage is the task token slice of a trace: the main agent and its
subagents (call_kind=main across every thread), no cache split or cost
(those live on the total Usage). Deliberately not spine-only — a
subagent doing the user's work is task spend, not shadow.
properties:
input_tokens:
format: int64
type: integer
output_tokens:
format: int64
type: integer
type: object
ModelUsage:
description: |-
ModelUsage is one model's contribution to a session in the API: how
many llm calls ran on it and what they spent. Cost-weighted (priced
at derive time) so a per-model share reflects spend, not call count.
properties:
calls:
format: int64
type: integer
cost_usd:
format: double
type: number
input_tokens:
format: int64
type: integer
model:
type: string
output_tokens:
format: int64
type: integer
type: object
RawTurnAttribution:
description: |-
RawTurnAttribution is the effective, repairable attribution projected over
an immutable raw turn. Raw payload and envelope bytes remain untouched.
properties:
harness_id:
type: string
harness_session_id:
type: string
parent_harness_session_id:
type: string
raw_turn_id:
format: int64
type: integer
thread_id:
type: string
type: object
RawTurnAttributionRepairRequest:
description: |-
RawTurnAttributionRepairRequest selects exactly one raw row and supplies a
complete replacement attribution. OrgID is supplied by the trusted caller
context, never by an HTTP request body.
properties:
harness_id:
type: string
harness_session_id:
type: string
paper_proxy_request_id:
type: string
parent_harness_session_id:
type: string
raw_turn_id:
format: int64
type: integer
reason:
type: string
thread_id:
type: string
type: object
RawTurnAttributionRepairResult:
properties:
effective:
$ref: '#/components/schemas/RawTurnAttribution'
previous:
$ref: '#/components/schemas/RawTurnAttribution'
projections_pending:
description: |-
ProjectionsPending lists the sessions whose synchronous rebuild failed
after the correction committed. Empty on full success. When set, the
correction is effective at read time and the listed projections
converge via the derive queue (marked dirty in the correction
transaction); the call returns ErrRepairProjectionsPending alongside
this result.
items:
$ref: '#/components/schemas/RepairPendingSession'
type: array
recorded:
type: boolean
source_cleanup_pending:
description: |-
SourceCleanupPending reports that the best-effort removal of the
emptied previous-session row failed after the correction and both
projection rebuilds applied. The leftover row is cosmetic — it anchors
no effective turns — and nothing retries the deletion automatically:
the flag makes the response honest about the leftover, it is not a
promise of later cleanup. On its own it never accompanies an error;
when ProjectionsPending is empty too, the repair succeeded.
type: boolean
type: object
RawTurnHeaderItem:
description: |-
RawTurnHeaderItem is one wire-log row: what crossed the wire (or
arrived as a transcript push), without the payload blobs. The
`source` field is the wire-vs-transcript distinction.
properties:
agent_name:
type: string
id:
format: int64
type: integer
meta:
type: object
provider:
type: string
received_at:
format: date-time
type: string
request_bytes:
format: int64
type: integer
request_id:
type: string
response_bytes:
format: int64
type: integer
source:
type: string
type: object
RawTurnListResponse:
description: RawTurnListResponse is a session's wire log.
properties:
items:
items:
$ref: '#/components/schemas/RawTurnHeaderItem'
type: array
type: object
ReconcileStats:
description: ReconcileStats reports the transcript↔wire fusion for one org.
properties:
codex_interacted_rows:
description: |-
CodexInteractedRows counts anchor rows carrying a non-started
kind (interacted re-entries banked by paperd for future
rendering). They are deliberately INERT: excluded from every
join, they perturb nothing but this counter — the visible proof
the rows arrived and were ignored by design (PCC-1021 C).
format: int32
type: integer
codex_threads_anchored:
description: |-
Codex thread-spawn anchoring (see codex.go). Unanchored threads
degrade to trace-root placement — a non-zero count is the visible
signal that spawn-anchor rows are missing or ambiguous.
format: int32
type: integer
codex_threads_unanchored:
format: int32
type: integer
conversation_joined:
description: |-
ConversationJoined / ConversationTotal measure how many
conversation-spine nodes' content appears in a transcript — the
Go-native version of the prototype's join-rate oracle.
format: int32
type: integer
conversation_total:
format: int32
type: integer
forked_chains:
format: int32
type: integer
main_chains_joined:
format: int32
type: integer
subagent_forks:
format: int32
type: integer
transcript_files:
format: int32
type: integer
type: object
RederiveReport:
description: RederiveReport summarizes one derive pass.
properties:
attached_verdicts:
format: int32
type: integer
call_kinds:
additionalProperties:
format: int32
type: integer
type: object
judged_actions:
description: |-
Verdict attach: judged actions grouped across stages, and how
many attached one-to-one to a captured tool_use.
format: int32
type: integer
node_kinds:
additionalProperties:
format: int32
type: integer
type: object
nodes:
format: int32
type: integer
parse_failures:
items:
type: string
type: array
parsed_turns:
format: int32
type: integer
plans_attached:
description: |-
PlansAttached counts plan-name-gen calls linked to the
ExitPlanMode tool_use that accepted the plan.
format: int32
type: integer
raw_only_turns:
format: int32
type: integer
raw_turns:
format: int32
type: integer
reconcile:
$ref: '#/components/schemas/ReconcileStats'
unattached_actions:
description: |-
UnattachedActions samples judged actions that found no matching
tool_use (capped) — expected for non-tool events like subagent
handbacks; anything else is matcher signal worth reading.
items:
type: string
type: array
web_summary_attached:
description: |-
WebSummaryAttached counts web-summary calls linked back to their
WebFetch/WebSearch tool_use.
format: int32
type: integer
type: object
Rejection:
description: |-
Rejection is a cassette core refused to serve, and why.
Rejections are first-class rather than log lines because they are the answer
to the question an operator actually asks — "why is my cassette not there?" —
and that question is asked over HTTP, from a machine that cannot read the
server's stderr.
properties:
reason:
description: Reason is the human-facing explanation. It is never parsed.
type: string
subject:
description: |-
Subject names what was rejected: the configured OpenAPI URL, with any
credential redacted.
type: string
type: object
RepairPendingSession:
description: |-
RepairPendingSession names one harness session whose projection rebuild
did not complete synchronously during a repair. The session is already
queued for the derive worker, which converges it.
properties:
harness_id:
type: string
harness_session_id:
type: string
type: object
SeedResult:
description: Result summarizes one seeding run.
properties:
raw_turns:
description: RawTurns is the total number of corpus rows replayed.
format: int32
type: integer
raw_turns_deduped:
format: int64
type: integer
raw_turns_inserted:
description: |-
RawTurnsInserted counts rows that landed as new raw turns;
RawTurnsDeduped counts replays the raw layer's dedup absorbed
(a re-seed reports everything deduped).
format: int64
type: integer
sessions:
description: Sessions is the number of demo sessions the corpora replay
into.
format: int32
type: integer
type: object
SessionDetailResponse:
description: |-
SessionDetailResponse is the response for GET /v1/sessions/:id: the
session record alone. The conversation content lives on the span model
(GET /v1/sessions/:id/traces).
properties:
session:
$ref: '#/components/schemas/SessionItem'
type: object
SessionItem:
description: |-
SessionItem is the per-session shape: capture identity at the top
level, the deriver-owned projection nested under `rollup`. The split
mirrors the storage rows — identity is ingest-written, rollup is
deriver-written — so the wire can't blur which layer owns a field.
properties:
auth_subject:
description: |-
AuthSubject is the gateway-stamped JWT subject (WorkOS user id)
captured at ingest; empty for rows captured before the edge began
stamping it.
type: string
cwd:
type: string
display_name:
description: |-
DisplayName is the user's Console rename (sessions.display_name),
empty unless a user set one. Written only by PATCH /v1/sessions/:id,
never by ingest, so it survives a live session. It is the top of the
DisplayTitle resolution; exposed raw so the edit affordance can seed
its input from the user's own title (not the resolved fallback).
type: string
display_title:
description: |-
DisplayTitle is the server-resolved label clients should render:
DisplayName -> rollup.title (generated) -> preview -> Name -> id
slice. Resolving once on the server keeps every client (Console,
paper CLI) from re-deriving — and diverging on — the precedence
(PCC-970). Never empty: it falls back to a short harness id slice, then
the session id (the primary key, always set for a stored row).
type: string
ended_at:
format: date-time
type: string
harness_id:
type: string
harness_metadata:
additionalProperties: {}
type: object
harness_session_id:
type: string
harness_version:
type: string
id:
description: Identity — capture-side facts, ingest-written.
type: string
last_seen_at:
format: date-time
type: string
live:
description: |-
Live is a runtime presence signal, not a projection fact: true when
the session has no recorded end and was seen within the liveness
window. Keyed on ended_at + last_seen_at recency (both ingest-fresh),
never on the derived status: an interactive session folds to a
terminal status (an end_turn assistant reply reads as "completed")
after every turn while still open, so status cannot gate liveness.
Computed at response time so the console renders it directly instead
of inferring "running" itself.
type: boolean
name:
description: |-
Name is the harness identity-row label — the harness-supplied session
name (a plan slug), or the folded title (rollup.title) as a fallback
when no name was captured. This is capture/deriver provenance, NOT a
user title: ingest re-sends it every turn. Clients should render
DisplayTitle, not Name (PCC-970).
type: string
parent_session_id:
type: string
rollup:
$ref: '#/components/schemas/SessionRollup'
started_at:
format: date-time
type: string
type: object
SessionListResponse:
description: SessionListResponse is the response envelope for GET /v1/sessions.
properties:
items:
items:
$ref: '#/components/schemas/SessionItem'
type: array
next_cursor:
type: string
type: object
SessionRollup:
description: |-
SessionRollup is the deriver-owned session projection — status, title,
counts, and spend, all folded from the span layer at derive time.
Every field is 'unknown'/zero/empty until the session first derives.
properties:
kind_counts:
additionalProperties:
format: int32
type: integer
description: |-
KindCounts (spans per call_kind) and Tasks (TaskCreate/TaskUpdate
folds) are pinned so the rollup shape is uniform across sessions.
type: object
model:
description: |-
Model is the dominant conversation-spine model; ModelUsage is the
per-model spend breakdown across every thread (subagent models
included), cost-ordered so the UI can show "dominant model + share"
without a cheap-subagent fan-out skewing it.
type: string
model_usage:
items:
$ref: '#/components/schemas/ModelUsage'
type: array
preview:
type: string
status:
type: string
tasks:
items:
$ref: '#/components/schemas/TreeTask'
type: array
title:
description: |-
Title is the deriver's folded session title (derived_title),
generated from the conversation. Empty until title generation
produces one. It never falls back to the identity-row name, so it is
the stable descriptive title clients prefer for display; the
identity-row label (harness name or rename) is SessionItem.Name.
type: string
turn_count:
format: int32
type: integer
usage:
$ref: '#/components/schemas/SessionUsage'
type: object
SessionTracesResponse:
description: |-
SessionTracesResponse is the composite session view on the span
model. `schema` stamps the projection generation the rows were derived
against, so the presentational shape can version independently.
properties:
links:
items:
$ref: '#/components/schemas/SpanLinkItem'
type: array
schema:
type: string
session:
$ref: '#/components/schemas/SessionItem'
traces:
items:
$ref: '#/components/schemas/TraceDetail'
type: array
type: object
SessionUsage:
description: |-
SessionUsage is the session's total token/cost spend, folded from the
span layer. Pinned (no omitempty) for a uniform object shape.
properties:
cost_usd:
format: double
type: number
input_tokens:
format: int64
type: integer
output_tokens:
format: int64
type: integer
type: object
SpanItem:
description: |-
SpanItem is one observed unit of work. Every field is a deriver
output, formatting-only: the harness-taxonomy fields (call_kind, model,
stop_reason, thread_id, verdict) are typed rather than bagged in a
metadata map, and input/output are uniform content-block arrays for
ALL kinds — the console owns per-kind rendering.
properties:
call_kind:
description: Deriver-written taxonomy, promoted from the old metadata grab-bag.
type: string
duration_ns:
format: int64
type: integer
input:
description: |-
Input/Output are content-block arrays (llm.ContentBlock), uniform for
every kind (tool spans included — no unwrapping). Pinned to [] when
empty.
items:
type: object
type: array
kind:
type: string
model:
type: string
name:
type: string
output:
items:
type: object
type: array
parent_span_id:
type: string
payload:
description: |-
Payload marks a preview-truncated span so the console drills in for
the full payload; absent in full mode.
type: string
raw_turn_id:
format: int64
type: integer
seq:
description: |-
Seq is the span's presentation ordinal within its trace; spans
arrive sorted by it (started_at ties inside one llm call — parallel
tool batches share an instant).
format: int64
type: integer
span_id:
type: string
started_at:
format: date-time
type: string
status:
type: string
stop_reason:
type: string
thread_id:
type: string
trace_id:
type: string
usage:
description: |-
Usage (was `metrics`) is an llm.Usage object on the wire — {}-pinned
for usage-less spans.
type: object
verdict:
description: |-
Verdict is the typed security-monitor disposition (null off
permission-check spans), deriver-written. It is a Verdict object or
null on the wire; the oas tag states that, because a json.RawMessage
carries no shape a reflector could recover.
nullable: true
type: object
type: object
SpanLinkItem:
description: |-
SpanLinkItem is a dataflow edge. kind is a typed top-level field
(rejoin / verdict / compaction-seam / emits / feeds); from/to trace ids
differ on cross-trace causality.
properties:
from_io:
type: string
from_span_id:
type: string
from_trace_id:
type: string
kind:
type: string
to_io:
type: string
to_span_id:
type: string
to_trace_id:
type: string
type: object
SpanSearchOutput:
description: SpanSearchOutput is the span search response.
properties:
count:
format: int32
type: integer
query:
type: string
results:
items:
$ref: '#/components/schemas/SpanSearchResult'
type: array
type: object
SpanSearchResult:
description: SpanSearchResult is one span hit with its trace/turn context.
properties:
model:
type: string
score:
format: float
type: number
session_id:
type: string
snippet:
description: Snippet previews the matched span's delta-only text.
type: string
span_id:
type: string
started_at:
format: date-time
type: string
trace_id:
type: string
user_prompt:
description: |-
UserPrompt is the prompt of the turn (trace) the span belongs to.
Served explicitly (not omitempty) so a synthetic turn's empty prompt
reaches consumers as "" rather than a dropped key — see TraceItem.
type: string
type: object
StatsResponse:
description: |-
StatsResponse is the response for GET /v1/stats.
The numbers come from the span-projection trace-grain rollups, so they
agree with the session detail and trace views:
- InputTokens / OutputTokens / TotalCost are SUMs of span_turns
rollups — delta-only per-call usage, never the re-sent history
(each main call re-bills the whole conversation on the wire).
- TotalDurationMs is the SUM of trace durations — agent time. Idle
time between turns does not count. Served in milliseconds, not the
nanoseconds we store: the summed ns over a wide window overflows a
JSON consumer's 2^53 safe-integer range (~104 cumulative days), and
sub-ms precision is meaningless for an aggregate agent-time figure.
- TurnCount counts traces (user-visible turns).
- ToolCalls is the SUM of the turn rollups' tool span counts,
windowed on the turn's started_at like every other figure here
rather than on each tool span's own timestamp (PCC-936).
- CompletedCount counts distinct sessions whose denormalized
derived_status is 'completed' (chain-aware, PCC-515).
properties:
completed_count:
format: int32
type: integer
input_tokens:
format: int64
type: integer
output_tokens:
format: int64
type: integer
session_count:
format: int32
type: integer
tool_calls:
format: int32
type: integer
total_cost:
format: double
type: number
total_duration_ms:
format: int64
type: integer
turn_count:
format: int32
type: integer
type: object
TraceDetail:
description: |-
TraceDetail is one trace with its spans. In the composite session
response links are session-scoped (top level); the single-trace
endpoint sets Links to the edges touching that trace. `schema` stamps
the projection generation on the STANDALONE /v1/traces/{id} response
(omitempty — the composite embeds TraceDetail and already carries one
stamp at the top level, so the embedded copies stay unstamped).
properties:
links:
items:
$ref: '#/components/schemas/SpanLinkItem'
type: array
schema:
type: string
spans:
items:
$ref: '#/components/schemas/SpanItem'
type: array
trace:
$ref: '#/components/schemas/TraceItem'
type: object
TraceItem:
description: |-
TraceItem is one user-visible turn's header. session_id / harness ids
are not duplicated here — they belong to the session. A trace's
post-compaction status is the typed Synthetic field below (promoted out
of the old metadata grab-bag); the same seam is also recoverable from
the session's compaction-seam links.
properties:
duration_ns:
format: int64
type: integer
ended_at:
format: date-time
type: string
main_usage:
$ref: '#/components/schemas/MainUsage'
response_preview:
description: |-
ResponsePreview is the derive-time fold of the closing
conversation-spine llm call's text output — the answer line for
collapsed turn cards, so summary consumers never need spans.
type: string
source:
description: |-
Source is the capture origin of the turn's rows ("wire" |
"transcript"), promoted from raw_turns.source. Per-trace, so a
session can mix live wire capture and transcript backfill. Today
every trace is "wire": transcripts only reconcile fork/parent edges
during derivation, they never form a trace on their own. "transcript"
becomes real when a session is reconstructed purely from a transcript
file with no proxy capture (an OSS backfill path).
type: string
span_count:
format: int32
type: integer
started_at:
format: date-time
type: string
status:
type: string
synthetic:
description: |-
Synthetic is a typed deriver signal ("post-compaction" for a
compaction continuation, "shadow-opener" for a shadow-only opener),
promoted out of the old metadata grab-bag. Absent for genuine
prompt-opened turns.
type: string
trace_id:
type: string
usage:
$ref: '#/components/schemas/TraceUsage'
user_prompt:
description: |-
UserPrompt is served explicitly (not omitempty): a synthetic opener
has an empty prompt, and dropping the key turns the empty string
into `undefined` on the wire, which breaks consumers that expect a
string (e.g. the console's stripHarnessTags). Empty means synthetic.
type: string
type: object
TraceListResponse:
description: |-
TraceListResponse is the summaries list for one session. `schema`
stamps the projection generation the rows were derived against — the
same stamp the composite carries — so every trace-grain response is
self-describing, not just the composite.
properties:
items:
items:
$ref: '#/components/schemas/TraceItem'
type: array
schema:
type: string
type: object
TraceUsage:
description: |-
TraceUsage is a trace's total token/cost rollup. Fields are pinned
(no omitempty) so the object shape is uniform across traces.
properties:
cache_creation_tokens:
format: int64
type: integer
cache_read_tokens:
format: int64
type: integer
cost_usd:
format: double
type: number
input_tokens:
format: int64
type: integer
output_tokens:
format: int64
type: integer
type: object
TreeTask:
description: |-
TreeTask is one task folded from the session's TaskCreate/TaskUpdate
calls.
properties:
description:
type: string
id:
type: string
status:
type: string
subject:
type: string
updates:
format: int32
type: integer
type: object
createSkillRequest:
description: |-
createSkillRequest is the POST /v1/skills body for an authored-from-scratch
skill — only a name is required; the rest default to an empty private draft.
properties:
content:
type: string
description:
type: string
name:
type: string
tags:
items:
type: string
type: array
type:
type: string
type: object
deriveRunResponse:
description: |-
deriveRunResponse is the derive-run result, keyed by org.
Declared as a type rather than assembled inline so the published schema is
generated from the shape the handler actually returns; a fiber.Map would
leave swag with nothing to describe and the endpoint documented as an opaque
object.
properties:
orgs:
additionalProperties:
$ref: '#/components/schemas/RederiveReport'
type: object
type: object
generateSkillRequest:
description: |-
generateSkillRequest is the POST /v1/skills/generate body. It mirrors the
console's GenerateSkillInput: the client nominates source sessions plus
optional hints, and the server is authoritative on the skill body. Wire
shape is camelCase to match the console's skills schemas (which predate and
diverge from the snake_case convention the rest of tapes uses).
properties:
hint:
properties:
description:
type: string
name:
type: string
tags:
items:
type: string
type: array
type:
type: string
type: object
sessionIds:
items:
type: string
type: array
type: object
publishSkillRequest:
description: publishSkillRequest is the POST /v1/skills/:slug/versions body.
properties:
changelog:
type: string
content:
type: string
type: object
seedDemoRequest:
properties:
overwrite:
type: boolean
type: object
sessionSkillsResponse:
description: |-
sessionSkillsResponse is the envelope for the skills attributed to one
session. Unpaginated: a session's skill count is bounded by what was
generated from it.
properties:
items:
items:
$ref: '#/components/schemas/skillResponse'
type: array
type: object
sessionUpdateRequest:
description: |-
sessionUpdateRequest is the PATCH /v1/sessions/:id body. DisplayName is a
pointer so an absent field (nil) is distinguishable from an explicit null
or an empty string, both of which mean "clear back to the auto-derived
title" (CC-3); an absent field is a 400 (nothing to update). The field is
display_name (not name) so the request matches the display_name it sets on
the response — and never the harness identity `name` (PCC-970).
properties:
display_name:
type: string
type: object
skillCountsResp:
description: |-
skillCountsResp are the tab counts for the current search: all matching,
authored by the caller (mine), and everyone else's (team = all - mine).
properties:
all:
format: int64
type: integer
mine:
format: int64
type: integer
team:
format: int64
type: integer
type: object
skillResponse:
description: |-
skillResponse is the unified Skill shape the console expects (camelCase). id
is the opaque identity / route key; slug is a cosmetic display label. content
always lives on the skill row (versions are history only); parentId is null
unless the skill is a duplicate/fork.
properties:
authorId:
type: string
content:
type: string
createdAt:
type: string
description:
type: string
downloadCount:
format: int64
type: integer
id:
type: string
isAiGenerated:
type: boolean
name:
type: string
originatingSessionIds:
items:
type: string
type: array
parentId:
type: string
slug:
type: string
tags:
items:
type: string
type: array
type:
type: string
updatedAt:
type: string
version:
type: string
visibility:
type: string
type: object
skillVersionResponse:
description: skillVersionResponse is one immutable published snapshot.
properties:
authorId:
type: string
changelog:
type: string
content:
type: string
id:
type: string
publishedAt:
type: string
semver:
type: string
skillId:
type: string
versionNumber:
format: int32
type: integer
type: object
skillVersionsResponse:
description: |-
skillVersionsResponse is the full version history for one skill, newest
first. TotalCount is the length of Versions — the history is returned whole
rather than paged, so the two never disagree.
properties:
totalCount:
format: int32
type: integer
versions:
items:
$ref: '#/components/schemas/skillVersionResponse'
type: array
type: object
skillsListResponse:
description: |-
skillsListResponse is the paginated list envelope: one keyset page plus the
opaque next_cursor (mirroring /v1/sessions) and the per-tab counts for the
active search.
properties:
counts:
$ref: '#/components/schemas/skillCountsResp'
items:
items:
$ref: '#/components/schemas/skillResponse'
type: array
next_cursor:
type: string
type: object
updateSkillRequest:
description: |-
updateSkillRequest is the PUT /v1/skills/:slug body — all fields optional;
only present fields are applied onto the existing record.
properties:
content:
type: string
description:
type: string
name:
type: string
tags:
items:
type: string
type: array
type:
type: string
visibility:
type: string
type: object
info:
description: |-
HTTP API for inspecting, querying, and searching stored Tapes sessions.
The REST surface exposes health checks, session listing and retrieval, derived session summaries, aggregate stats, semantic search, skill authoring and publishing, operator maintenance endpoints, and a streamable MCP endpoint.
Not covered here: the ingest write surface, which is a separate server that publishes its own contract at its own /openapi.
title: Tapes API
version: "1.0"
openapi: 3.0.3
paths:
/ping:
get:
description: Returns a simple JSON string confirming that the API server is
reachable.
operationId: ping
responses:
"200":
content:
application/json:
schema:
type: string
description: pong
summary: Health check
tags:
- health
/v1/admin/derive/run:
post:
description: |-
Rebuilds traces, spans, links, and session rollups for every org from the immutable raw-turn store. Idempotent: re-running reproduces the same projection and prunes rows the current derive no longer emits.
This is how a projection or classifier change reaches already-captured data — it re-derives rather than re-captures. Cost scales with the raw layer, so it is an operator lever, not a request-path call.
operationId: runDerive
responses:
"200":
content:
application/json:
schema:
$ref: '#/components/schemas/deriveRunResponse'
description: Per-org derive reports
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Derive failed
"501":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Driver does not host the raw-turn layer
summary: Re-derive the span projection (operator)
tags:
- admin
/v1/admin/raw-turns/attribution-repair:
post:
description: Records an audited, append-only attribution correction without
modifying raw_turns, then synchronously re-derives the previous and effective
sessions. Select exactly one row by raw_turn_id or paper_proxy_request_id.
operationId: repairRawTurnAttribution
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/RawTurnAttributionRepairRequest'
description: Attribution repair
required: true
responses:
"200":
content:
application/json:
schema:
$ref: '#/components/schemas/RawTurnAttributionRepairResult'
description: Repair applied; source_cleanup_pending discloses a cosmetic
leftover source session row that nothing retries automatically
"202":
content:
application/json:
schema:
$ref: '#/components/schemas/RawTurnAttributionRepairResult'
description: Correction recorded; projections_pending lists sessions the
derive worker will converge
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Invalid payload or replacement attribution
"404":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Raw turn not found
"409":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Correlation selector is ambiguous
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Repair failed
"501":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Driver does not support attribution repair
summary: Repair raw-turn attribution (operator)
tags:
- admin
/v1/admin/seed/demo:
post:
description: 'Replays the bundled demo capture corpora through the ingest write
path into the caller''s org, then derives the seeded sessions. Idempotent:
raw-turn dedup makes repeat seeds no-ops.'
operationId: seedDemo
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/seedDemoRequest'
description: Seed options (overwrite is no longer supported)
responses:
"200":
content:
application/json:
schema:
$ref: '#/components/schemas/SeedResult'
description: What was seeded
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Invalid payload or unsupported option
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Seeding failed
"501":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Driver does not host the raw-turn layer
summary: Seed demo sessions (operator)
tags:
- admin
/v1/cassettes:
get:
description: Lists the cassettes served by this API, their public route and
OpenAPI paths, and any configured cassette sources that could not be loaded.
operationId: listCassettes
responses:
"200":
content:
application/json:
schema:
$ref: '#/components/schemas/Discovery'
description: What is installed here, and what failed
summary: Discover installed cassettes
tags:
- cassettes
/v1/mcp:
delete:
description: Requests termination of a streamable MCP session when a client
is using session-oriented transport semantics.
operationId: closeMcpSession
responses:
"200":
content:
application/json:
schema:
$ref: '#/components/schemas/MCPResponse'
description: Session closed
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/MCPResponse'
description: Invalid request
summary: Close an MCP session
tags:
- mcp
get:
description: Opens the streamable MCP endpoint for server-sent events. Stateless
clients can use this to receive streamed MCP messages.
operationId: openMcpStream
responses:
"200":
content:
text/event-stream:
schema:
type: string
description: Server-sent event stream
summary: Open an MCP event stream
tags:
- mcp
post:
description: |-
Sends a JSON-RPC 2.0 request to the stateless Model Context Protocol endpoint mounted at /v1/mcp.
Typical calls include initialize, tools/list, and tools/call. The server exposes tools advertised by installed cassettes, plus the legacy core search tool while search is configured in core.
operationId: invokeMcp
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/MCPRequest'
description: JSON-RPC 2.0 request
required: true
responses:
"200":
content:
application/json:
schema:
$ref: '#/components/schemas/MCPResponse'
description: JSON-RPC 2.0 response
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/MCPResponse'
description: Invalid JSON-RPC request
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/MCPResponse'
description: Server-side MCP error
summary: Invoke the streamable MCP endpoint
tags:
- mcp
/v1/search/spans:
get:
description: Embeds the query text and runs vector similarity over the embedded
span projection (main llm spans, delta-only content). Each hit carries span,
trace, and turn context.
operationId: searchSpans
parameters:
- description: Search query
in: query
name: query
required: true
schema:
type: string
- description: Maximum number of results to return
in: query
name: top_k
schema:
default: 5
minimum: 1
type: integer
responses:
"200":
content:
application/json:
schema:
$ref: '#/components/schemas/SpanSearchOutput'
description: Search hits
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Missing or invalid query parameters
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Search execution failed
"503":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Span search is not configured or not yet initialized
summary: Semantic search over span embeddings
tags:
- search
/v1/sessions:
get:
description: Returns one row per harness session from the sessions table, cursor-paginated.
Default order is last_active (last_seen_at) desc; override with the sort and
direction query params.
operationId: listSessions
parameters:
- description: Maximum number of sessions to return (default 50, max 200)
in: query
name: limit
schema:
minimum: 1
type: integer
- description: Opaque pagination cursor returned by a previous response
in: query
name: cursor
schema:
type: string
- description: 'Sort column: last_active|started_at|turn_count|total_cost_usd|total_tokens|duration_ns|derived_status|auth_subject
(default last_active)'
in: query
name: sort
schema:
type: string
- description: 'Sort direction: asc|desc (default desc)'
in: query
name: direction
schema:
enum:
- asc
- desc
type: string
- description: Only include sessions with a turn started at or after this RFC3339
timestamp (activity window, matches /v1/stats)
in: query
name: since
schema:
format: date-time
type: string
- description: Only include sessions with a turn started before this RFC3339
timestamp (activity window, matches /v1/stats)
in: query
name: until
schema:
format: date-time
type: string
- description: 'Combined with harness_session_id, narrows the filter to the
single session with this harness id (exact match). Rejected alone (400):
a harness id names a harness, not a session. Incompatible with cursor, sort,
direction, since, and until (400); limit is ignored when the filter is active'
in: query
name: harness_id
schema:
type: string
- description: Filter to sessions with this harness session id (exact match).
Alone it matches across all harnesses — the id is unique per harness, so
at most one row per harness returns, in practice zero or one; with harness_id
it is a single-harness point lookup. Incompatible with cursor, sort, direction,
since, and until (400); limit is ignored when the filter is active
in: query
name: harness_session_id
schema:
type: string
- description: Filter the paged list to sessions captured for this gateway-stamped
JWT subject (exact match; ignored on the harness filter path)
in: query
name: auth_subject
schema:
type: string
responses:
"200":
content:
application/json:
schema:
$ref: '#/components/schemas/SessionListResponse'
description: One page of sessions
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Invalid query parameters, a lone harness_id, or cursor, sort,
direction, since, or until combined with the harness filter
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Failed to list sessions
"501":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Sessions not supported by this backend
summary: List sessions
tags:
- sessions
/v1/sessions/{id}:
delete:
description: 'Permanently deletes a session and its subtree: subagent child
sessions and their derived traces/spans cascade with it. The immutable raw_turns
capture log is left intact.'
operationId: deleteSession
parameters:
- description: Session id (UUID)
in: path
name: id
required: true
schema:
type: string
responses:
"204":
description: Session deleted
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Missing or malformed id
"404":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Session not found
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Failed to delete session
"501":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Sessions not supported by this backend
summary: Delete a session
tags:
- sessions
get:
description: 'Returns a single session record. The conversation content lives
on the span model: GET /v1/sessions/{id}/traces.'
operationId: getSession
parameters:
- description: Session id (UUID)
in: path
name: id
required: true
schema:
type: string
responses:
"200":
content:
application/json:
schema:
$ref: '#/components/schemas/SessionDetailResponse'
description: The session
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Missing or malformed id
"404":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Session not found
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Failed to load session
"501":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Sessions not supported by this backend
summary: Get a session
tags:
- sessions
patch:
description: Updates the user-editable display_name. An absent field is a 400;
null or empty (after trim) clears back to the auto-derived title. Length is
bounded to 200 characters.
operationId: updateSession
parameters:
- description: Session id (UUID)
in: path
name: id
required: true
schema:
type: string
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/sessionUpdateRequest'
description: Update request
required: true
responses:
"200":
content:
application/json:
schema:
$ref: '#/components/schemas/SessionDetailResponse'
description: The updated session
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Missing/malformed id, missing display_name field, or display_name
exceeds 200 characters
"404":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Session not found or not in caller's org
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Failed to update session
"501":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Sessions not supported by this backend
summary: Update a session's title
tags:
- sessions
/v1/sessions/{id}/export:
get:
description: 'Returns the session as a single JSON line (downloadable attachment):
the session object with its traces, each trace carrying its full spans — the
same shape as GET /v1/sessions/{id}/traces with payload=full. detail=traces
exports turn headers only (no spans or links).'
operationId: exportSession
parameters:
- description: Session id (UUID)
in: path
name: id
required: true
schema:
type: string
- description: 'Export granularity: spans (default, traces with full spans)
or traces (turn headers only)'
in: query
name: detail
schema:
enum:
- spans
- traces
type: string
responses:
"200":
content:
application/x-ndjson:
schema:
type: string
description: JSONL body, one session object with nested traces (and spans
at detail=spans)
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Missing or malformed id, or unrecognized detail
"404":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Session not found
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Failed to load or render the session
"501":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Sessions not supported by this backend
summary: Export a session as JSONL
tags:
- sessions
/v1/sessions/{id}/raw_turns:
get:
description: 'The raw layer''s wire log: one row per captured call or transcript
push, identity and sizes only. `source` distinguishes what crossed the wire
from what the harness pushed as its own account.'
operationId: listRawTurns
parameters:
- description: Session id (UUID)
in: path
name: id
required: true
schema:
type: string
responses:
"200":
content:
application/json:
schema:
$ref: '#/components/schemas/RawTurnListResponse'
description: The session's raw turn headers
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Missing or malformed id
"404":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Session not found
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Failed to list raw turns
"501":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Raw turns not supported by this backend
summary: List a session's raw capture log (operator)
tags:
- sessions
/v1/sessions/{id}/skills:
get:
description: Every skill generated from the given session. Unpaginated — the
count is bounded by what was generated from that one session.
operationId: listSessionSkills
parameters:
- description: Session id
in: path
name: id
required: true
schema:
type: string
responses:
"200":
content:
application/json:
schema:
$ref: '#/components/schemas/sessionSkillsResponse'
description: The session's skills
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Listing failed
"501":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Backend does not support skills
summary: List a session's skills
tags:
- skills
/v1/sessions/{id}/traces:
get:
description: Returns the session's user-visible turns as traces with nested
spans (llm calls, tools, subagents, shadow calls, injected context) and dataflow
links. Cross-trace links (compaction seams) are at the response top level.
operationId: getSessionTraces
parameters:
- description: Session id (UUID)
in: path
name: id
required: true
schema:
type: string
- description: 'Span payload mode: full (default) or preview (strings truncated;
fetch the span endpoint for full payloads)'
in: query
name: payload
schema:
enum:
- full
- preview
type: string
responses:
"200":
content:
application/json:
schema:
$ref: '#/components/schemas/SessionTracesResponse'
description: The session's traces and spans
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Missing or malformed id
"404":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Session not found
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Failed to load session
"501":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Span traces not supported by this backend
summary: Get a session's trace/span projection
tags:
- sessions
/v1/sessions/export:
get:
description: Streams one JSON line per session in the given window, newest-first,
as a downloadable attachment. Each line is the session object with its traces,
each trace carrying its full spans — the same shape as GET /v1/sessions/{id}/traces
with payload=full. detail=traces exports turn headers only (no spans or links).
Defaults to the trailing 30 days. Not bounded by the /v1/sessions list cap
— pages internally.
operationId: exportSessions
parameters:
- description: 'Only include sessions with a turn started at or after this RFC3339
timestamp (activity window; default: now - 30 days)'
in: query
name: since
schema:
format: date-time
type: string
- description: Only include sessions with a turn started before this RFC3339
timestamp (activity window)
in: query
name: until
schema:
format: date-time
type: string
- description: 'Export granularity: spans (default, traces with full spans)
or traces (turn headers only)'
in: query
name: detail
schema:
enum:
- spans
- traces
type: string
responses:
"200":
content:
application/x-ndjson:
schema:
type: string
description: JSONL body, one JSON object per session with nested traces
(and spans at detail=spans)
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Malformed since/until, or unrecognized detail
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Failed to list or render sessions
"501":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Sessions not supported by this backend
summary: Export sessions in a time window as JSONL
tags:
- sessions
/v1/skills:
get:
description: |-
One keyset page of the org's skills, newest-edited first, plus per-tab counts for the active search. Pagination mirrors /v1/sessions: pass the returned next_cursor to continue; its absence means the last page.
The counts are computed over the whole matching set rather than the loaded page, so a filtered tab shows a true total.
operationId: listSkills
parameters:
- description: Page size (default 24, max 100)
in: query
name: limit
schema:
minimum: 1
type: integer
- description: Opaque keyset cursor from a previous next_cursor. Reset it when
changing sort.
in: query
name: cursor
schema:
type: string
- description: Search over name, description, and tags
in: query
name: q
schema:
type: string
- description: Which slice to return
in: query
name: scope
schema:
enum:
- all
- mine
- team
type: string
- description: Ordering; defaults to most recently updated
in: query
name: sort
schema:
enum:
- downloads
type: string
responses:
"200":
content:
application/json:
schema:
$ref: '#/components/schemas/skillsListResponse'
description: One page of skills
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Malformed cursor
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Listing failed
"501":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Backend does not support skills
summary: List skills
tags:
- skills
post:
description: Creates a skill authored by hand, as opposed to the generator.
The caller supplies the content; nothing is inferred.
operationId: createSkill
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/createSkillRequest'
description: Skill to create
required: true
responses:
"201":
content:
application/json:
schema:
$ref: '#/components/schemas/skillResponse'
description: The created skill
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Invalid body or unknown type
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Create failed
"501":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Backend does not support skills
summary: Create a skill
tags:
- skills
/v1/skills/{id}:
delete:
description: Deletes the skill and its version history. Only the creator may
delete; another member of the same org gets 403 rather than 404, so the skill's
existence is not hidden from someone who can already list it.
operationId: deleteSkill
parameters:
- description: Skill id
in: path
name: id
required: true
schema:
type: string
responses:
"204":
description: Deleted
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Only the creator can delete this skill
"404":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Skill not found
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Delete failed
"501":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Backend does not support skills
summary: Delete a skill
tags:
- skills
get:
description: Returns one skill by its opaque id. The id is the route key; slug
is a cosmetic display label and is not addressable.
operationId: getSkill
parameters:
- description: Skill id
in: path
name: id
required: true
schema:
type: string
responses:
"200":
content:
application/json:
schema:
$ref: '#/components/schemas/skillResponse'
description: The skill
"404":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Skill not found
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Lookup failed
"501":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Backend does not support skills
summary: Get a skill
tags:
- skills
put:
description: Partial update of the skill head. Every field is optional; omitted
fields are left as they are. Editing the head does not publish — use the versions
endpoint to snapshot.
operationId: updateSkill
parameters:
- description: Skill id
in: path
name: id
required: true
schema:
type: string
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/updateSkillRequest'
description: Fields to change
required: true
responses:
"200":
content:
application/json:
schema:
$ref: '#/components/schemas/skillResponse'
description: The updated skill
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Invalid body or unknown type
"404":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Skill not found
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Save failed
"501":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Backend does not support skills
summary: Update a skill
tags:
- skills
/v1/skills/{id}/duplicate:
post:
description: Forks a skill into a new one owned by the caller, with parentId
set to the source. The copy starts its own version history; the source is
untouched.
operationId: duplicateSkill
parameters:
- description: Skill id to duplicate
in: path
name: id
required: true
schema:
type: string
responses:
"201":
content:
application/json:
schema:
$ref: '#/components/schemas/skillResponse'
description: The duplicated skill
"404":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Skill not found
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Duplicate failed
"501":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Backend does not support skills
summary: Duplicate a skill
tags:
- skills
/v1/skills/{id}/skill.md:
get:
description: |-
Renders the skill as an on-disk SKILL.md, served as an attachment. The frontmatter name is the kebab slug, which is what a harness matches to the skill's directory — not the human display name.
Serving this counts a download, best-effort: a failed counter write never fails the download.
operationId: getSkillMarkdown
parameters:
- description: Skill id
in: path
name: id
required: true
schema:
type: string
responses:
"200":
content:
text/markdown:
schema:
type: string
description: SKILL.md document
"404":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Skill not found
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Lookup failed
"501":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Backend does not support skills
summary: Download a skill as SKILL.md
tags:
- skills
/v1/skills/{id}/versions:
get:
description: Full published history for one skill, newest first. Returned whole
rather than paged, so totalCount is always the length of versions.
operationId: listSkillVersions
parameters:
- description: Skill id
in: path
name: id
required: true
schema:
type: string
responses:
"200":
content:
application/json:
schema:
$ref: '#/components/schemas/skillVersionsResponse'
description: The skill's versions
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Listing failed
"501":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Backend does not support skills
summary: List a skill's versions
tags:
- skills
post:
description: 'Snapshots the skill''s current content as an immutable version
and advances the skill''s semver. Versions are history: the head content stays
on the skill row, so reading a skill never needs its versions.'
operationId: publishSkill
parameters:
- description: Skill id
in: path
name: id
required: true
schema:
type: string
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/publishSkillRequest'
description: Version metadata
required: true
responses:
"201":
content:
application/json:
schema:
$ref: '#/components/schemas/skillVersionResponse'
description: The published version
"404":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Skill not found
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Publish failed, or the version landed but the head could not
be advanced
"501":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Backend does not support skills
summary: Publish a skill version
tags:
- skills
/v1/skills/generate:
post:
description: |-
Runs the LLM skill generator over the nominated sessions and persists the result. The client nominates sources and optional hints; the server is authoritative on the skill body.
Source sessions are read through an org-scoped in-process querier, so generation only ever sees sessions in the caller's tenant.
operationId: generateSkill
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/generateSkillRequest'
description: Source sessions and optional hints
required: true
responses:
"201":
content:
application/json:
schema:
$ref: '#/components/schemas/skillResponse'
description: The generated skill
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Invalid body, or sessionIds missing/empty
"404":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: One or more source sessions were not found
"422":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Sources carried nothing the generator could use
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Generation or persistence failed, or no LLM provider is configured
"501":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Backend does not support skills
summary: Generate a skill from sessions
tags:
- skills
/v1/stats:
get:
description: Returns counts plus cost / token / duration / tool-call / completed-count
totals for the window. The numbers are span-grain trace rollup sums (delta-only
usage, agent time = sum of trace durations) so they agree with the session
and trace views; turn_count counts traces. Filter the window with since/until,
and narrow every total to one user with auth_subject — the same subject the
/v1/sessions filter takes, so a personal surface can show totals that match
the rows beside them.
operationId: getStats
parameters:
- description: Only include records at or after this RFC3339 timestamp
in: query
name: since
schema:
format: date-time
type: string
- description: Only include records before or at this RFC3339 timestamp
in: query
name: until
schema:
format: date-time
type: string
- description: Narrow every total to sessions captured for this gateway-stamped
JWT subject (exact match). Omitted, the totals are org-wide. A subject with
no sessions in the window aggregates to zeros, not an error
in: query
name: auth_subject
schema:
type: string
responses:
"200":
content:
application/json:
schema:
$ref: '#/components/schemas/StatsResponse'
description: Aggregate stats for the window
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Invalid query parameters
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Failed to compute stats
summary: Get aggregate session stats
tags:
- sessions
/v1/traces:
get:
description: Returns turn headers for a session — no span payloads. Fetch GET
/v1/traces/{trace_id} per turn for spans and links.
operationId: listTraces
parameters:
- description: Session id (UUID)
in: query
name: session_id
required: true
schema:
type: string
responses:
"200":
content:
application/json:
schema:
$ref: '#/components/schemas/TraceListResponse'
description: The session's trace summaries
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Missing or malformed session_id
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Failed to list traces
"501":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Traces not supported by this backend
summary: List a session's traces (summaries)
tags:
- traces
/v1/traces/{trace_id}:
get:
description: 'Returns one user-visible turn: its spans nested by parent_span_id
and its dataflow links (links touching other traces included).'
operationId: getTrace
parameters:
- description: Trace id
in: path
name: trace_id
required: true
schema:
type: string
- description: 'Span payload mode: full (default) or preview (strings truncated;
fetch the span endpoint for full payloads)'
in: query
name: payload
schema:
enum:
- full
- preview
type: string
responses:
"200":
content:
application/json:
schema:
$ref: '#/components/schemas/TraceDetail'
description: The trace
"404":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Trace not found
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Failed to load trace
"501":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Traces not supported by this backend
summary: Get one trace with spans and links
tags:
- traces
/v1/traces/{trace_id}/spans/{span_id}:
get:
description: 'The payload drill-in: one span''s complete input/output content.'
operationId: getSpan
parameters:
- description: Trace id
in: path
name: trace_id
required: true
schema:
type: string
- description: Span id
in: path
name: span_id
required: true
schema:
type: string
responses:
"200":
content:
application/json:
schema:
$ref: '#/components/schemas/SpanItem'
description: The span
"404":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Span not found
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Failed to load span
"501":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: Spans not supported by this backend
summary: Get one span with full payloads
tags:
- traces