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