HTTP APIs
Tapes publishes two separate contracts because reading derived telemetry and ingesting trusted captures have different trust models.
Read API
Section titled “Read API”The default read API listens on :8081. It serves health, derived data, search, skills, operator maintenance, MCP, and its own OpenAPI contract.
| Area | Routes |
|---|---|
| Health and contract | GET /ping, GET /openapi, GET /swagger, GET /metrics |
| Browser UI | GET /, served only with --api-web-ui |
| Sessions | /v1/sessions, /v1/sessions/{id}, /v1/sessions/{id}/traces, /v1/sessions/{id}/raw_turns, /v1/sessions/{id}/export, /v1/sessions/export |
| Traces and spans | /v1/traces, /v1/traces/{trace_id}, /v1/traces/{trace_id}/spans/{span_id} |
| Search and aggregates | GET /v1/search/spans, GET /v1/stats |
| Skills | /v1/skills, /v1/skills/{id}, /v1/skills/{id}/versions, /v1/skills/{id}/duplicate, /v1/skills/{id}/skill.md, /v1/skills/generate, /v1/sessions/{id}/skills |
| MCP | /v1/mcp |
| Operator actions | /v1/admin/derive/run, /v1/admin/seed/demo, /v1/admin/raw-turns/attribution-repair |
| Cassettes | GET /v1/cassettes, GET /v1/cassettes/{name}/openapi.json, /v1/cassettes/{name}, /v1/cassettes/{name}/* |
GET /metrics is Prometheus exposition, deliberately outside any auth group and not described in the contract. GET / is HTML, not API surface, and is not described either.
The authoritative parameters, schemas, and methods are compiled from route registrations and served by the running API at GET /openapi. The aggregate includes admitted cassette operations. See Cassettes for their manifest and proxy contract. Notable current behavior:
- session listing is cursor-paginated;
- session and trace/span paths use UUID IDs;
- session content is read through traces and spans;
- semantic search exists only at
/v1/search/spans; - raw turns remain available at
/v1/sessions/{id}/raw_turns.
There is no /v1/search, /v1/sessions/summary, or hash-based session route.
Both contracts are sealed
Section titled “Both contracts are sealed”No generated OpenAPI document is checked in — a copy of what the server states
exactly is a copy that can go stale. What is checked in is a seal: api/CONTRACT
and ingest/CONTRACT each hold a sha256 fingerprint of that surface’s compiled
document with prose stripped out.
A test recompiles the document and compares. Move a route, a parameter, a schema,
or a status code and the test fails until the new value is written into the
CONTRACT file — which turns “this changes a published contract” into a line in
the diff instead of something a reviewer has to notice. Editing a doc comment is
deliberately not a contract event; text declared inline on a route registration
is published surface and does move the seal.
Consumers of these contracts live outside this repository, which is why the ingest surface in particular is sealed rather than merely tested: an unannounced change to it is one every capture adapter discovers in production.
Attribution repair
Section titled “Attribution repair”POST /v1/admin/raw-turns/attribution-repair records an audited, append-only attribution correction for exactly one raw turn — selected by raw_turn_id or paper_proxy_request_id — without modifying raw_turns, then synchronously re-derives the previous and effective sessions.
A 200 is a completed repair. A 202 means the correction committed and is effective, but the synchronous projection rebuild did not finish: projections_pending names the stale sessions, which the derive worker converges on its own — the repair is recorded, so do not retry it.
source_cleanup_pending is independent of the status code and can accompany either. It discloses an emptied source-session row the cleanup step failed to delete: cosmetic, anchoring no effective turns, and — unlike projections_pending — retried by nothing. It does not resolve on its own.
Private ingest API
Section titled “Private ingest API”The private ingest API defaults to :8082 and serves its separate contract at GET /openapi. The all-in-one tapes serve stack starts it alongside the proxy and read API; tapes serve ingest runs it as a standalone sidecar. Its write routes are:
POST /v1/ingest— append one completed conversation turn;POST /v1/ingest/transcript— append one harness transcript file or spawn-anchor row;GET /ping— health.
A transcript payload is the main session transcript, one subagent’s transcript, or a Codex spawn-anchor row (a sub_agent_activity rollout record). agent_id and tool_use_id carry the subagent fork edge the deriver reconciles against the wire capture. The optional kind field qualifies Codex anchor rows: absent or empty means spawn evidence; "interacted" marks a re-entry record (send_message, followup_task) that is stored for future rendering and deliberately ignored by derivation. Rows deduplicate on a content hash of records, so re-uploading unchanged content is a no-op while a grown transcript appends a new version; the deriver reads the latest version per (session, agent, lifecycle kind), so an interacted row never supersedes a spawn anchor.
Run the standalone form only for sidecar/gateway capture:
tapes serve ingest --postgres "$TAPES_STORAGE_POSTGRES_DSN"Request bodies are capped at roughly 14.67 MiB. A POST over the limit is rejected with 413 and the surface’s standard JSON error envelope ({"error": "..."}), the same shape as every other rejection, so capture adapters can parse all failures uniformly. Each body-limit rejection is counted in tapes_ingest_writes_total{provider="unknown",status="reject_oversize"} (the body is never parsed, so the provider is unknown) and logged with the declared content length, the configured limit, and the request path.
The ingest server appends to immutable raw_turns; it does not provide the read API. Treat it as a private trusted write surface, not as a public application endpoint. Authentication, network policy, and gateway grants are deployment responsibilities.
Provider proxy
Section titled “Provider proxy”The capture proxy defaults to :8080. It exposes provider-compatible request paths, not the Tapes read contract. Clients send LLM traffic to the proxy; they send inspection/search requests to :8081.
CORS and exposure
Section titled “CORS and exposure”Do not infer a production security boundary from local listen defaults or generated OpenAPI. Choose network exposure, TLS, authentication, tenant headers, and access control for the deployment environment. Tapes documentation intentionally does not prescribe a hosting redirect or public deployment topology.