Skip to content

Local HTTP API

The optional server dependencies expose one versioned API for an opened workspace. The host entry point is python -m tychora.interfaces.http.server WORKSPACE [--port PORT]; it binds one Uvicorn worker to 127.0.0.1 and publishes the bound authority in the private .runtime/endpoint.json record. connect_local(WORKSPACE) verifies that record, the live owner lock, the workspace UUID, protocol version, and the HTTP handshake before returning a blocking client. Tychora.connect(endpoint, credential, workspace_id) and AsyncTychora.connect(endpoint, credential, workspace_id) are the explicit credentialed client constructors; the async client verifies its handshake before its first operation. A stale record or a different endpoint identity fails closed. The runtime record contains a bearer secret, so its directory and file must remain owner-only. Credentials are sent in Authorization, never in URLs.

create_app(owner, services, auth) borrows an already-open owner. Its FastAPI lifespan drains the current service binding's accepted source jobs. The server context owns final owner closure, with a fallback drain if startup or shutdown fails. HTTP and the opened workspace's sync/async public Python facades share one ServiceRuntime admission gate; an operation pins its service binding through completion. A later settings activation replaces that authoritative binding, and the Python bridge follows the new source scheduler. Embedded transports sharing this owner must enter the same gate and resolve current services per operation; they must not drive a retained Services object independently. The host is a local, single-installation-principal service. Separate bearer tokens may have narrower collection, use, read, write, and manage grants; every operation intersects the token grant with the owner's scope and any per-request X-Tychora-Scope. Tokens naming a different principal are rejected. Same-principal tokens share that principal's history and idempotency namespace, subject to each token's effective scope and the durable job authorization receipt. This is not multi-user identity isolation.

Every request needs exactly one Host equal to the bound numeric loopback authority and exactly one Authorization: Bearer …. Native clients may omit Origin; when present, exactly one Origin must equal http:// plus that authority. Duplicate control headers are rejected before route parsing. Actual incoming body bytes are limited to 1 MiB, including requests without Content-Length; malformed or oversized length is rejected before body parsing. Last-Event-ID must be a nonnegative signed 64-bit decimal integer. No wildcard CORS or remote bind is configured. Source paths require write scope and must resolve within configured ingest roots. A request's response or resource-specific bounds may be smaller.

The API returns schema-versioned canonical error envelopes containing code, sanitized message, and retryable; envelopes are under 8 KiB. Invalid requests and budgets use HTTP 400, scope denial 403, missing resources 404, busy/profile/idempotency conflicts 409, removed original 410, unsupported media 406, unavailable capability/index/provider 503, and deadlines 504.

Route Contract
GET /v1/handshake Protocol, workspace and instance identities, effective token scope, and accessible default collection ID.
GET /v1/discovery[/{subject}] Scoped, cursor-bounded definitions, counts, and capabilities. Subjects include kinds, facets, relations, profiles, languages, sources, settings, runs, and models.
POST /v1/sources; GET /v1/sources[/{id}]; GET /v1/sources/{id}/revisions; POST /v1/sources/{id}/sync Source registration and source-specific sync need write; accepted operations return HTTP 202 with a durable JobHandle. Read routes expose scoped retained metadata. Source and revision lists take cursor and limit (1–100), query bounded database pages, and return at most 1 MiB. Source definitions preview one revision with next_revision_cursor; revision pages preview at most eight extractions and 16 channels each, marking truncation explicitly. Source input uses a UUID collection ID and registered kind such as manual@1; the literal default resolves only when that collection is granted.
GET /v1/sources/{id}/revisions/{revision_id}/extractions; GET /v1/sources/{id}/revisions/{revision_id}/extractions/{extraction_id}/channels Continue the bounded coverage preview after a readable revision. Extraction pages have limit 1–20 and channel pages 1–100; both return typed rows and next_cursor. The sync/async local and HTTP public clients expose sources.extractions(...) and sources.channels(...), alongside sources.revisions(...). A cursor is HMAC-bound to this owner instance, the effective grant, resource and page size. It expires when that owner instance closes or the grant changes; an invalid or cross-scope cursor gets 400. Source/revision SQL selects visible rows before emitting a cursor, so hidden row IDs never become continuation keys.
GET /v1/sources/{id}/revisions/{revision_id}/extractions/{extraction_id}/selections List policy-scoped retained image dimensions and native text selectors for an explicit selected-vision POST /v1/sources. Takes limit 1–50 and a scope-bound cursor; returns original/extraction hashes. The caller supplies task, schema, exact pixel rectangle and optional context intervals in the ordinary source request.
GET /v1/evidence/{id}; GET /v1/assets/{id}; GET /v1/assets/{id}/content; POST /v1/relations Scoped bounded inspection; asset bytes come only from registered asset bindings.
POST /v1/queries; GET /v1/queries[/{id}]; POST /v1/queries/{id}/replay Query execution and scoped retained history. Search returns the verified response assembler bytes. The default Accept: application/json uses canonical JSON; application/vnd.tychora.hierarchical+json selects the hierarchical projection. Unsupported media returns 406. Default metadata-only history cannot replay a request body and returns REPLAY_UNAVAILABLE (409).
GET /v1/jobs[/{id}]; POST /v1/jobs/{id}/cancel; POST /v1/jobs/{id}/retry; GET /v1/jobs/{id}/events Scoped durable status and control. Cancel authorization is durable before the local worker is signaled. SSE Last-Event-ID resumes at strictly greater sequence; expiration emits refresh, requiring a status read. Stream disconnect does not cancel the job.
POST /v1/jobs; POST /v1/evaluations; POST /v1/evaluations/datasets; GET /v1/evaluations[/{id}] Management and evaluation operations run under management scope. The evaluation dataset upload uses bounded manifest/JSONL bytes. Generic maintenance sync remains manage, while source-specific sync is write. Health with { "operation": "health", "verify_native": true } requires a whole-workspace read grant covering all collections, access floors, and current or retained uses; restricted readers are denied before this audit of ready native projections in the active snapshot; it reports verified and skipped-pending counts without repair. Unavailable optional channels return typed capability errors.
GET /v1/settings; PUT /v1/settings GET returns redacted effective settings. PUT needs manage and exactly { "profile": "…", "workspace_toml": "…" }, with no unknown fields. The selected profile must be registered and equal the submitted TOML's [defaults].profile; this durable selector is used on ordinary reopen. Older files without a default select local. Secrets remain references in TOML.

Idempotency-Key binds canonical source intent. Duplicate pending submissions return the same durable handle and share one execution; a conflicting intent returns 409. Accepted workers run on the owner's source-job loop independently of the HTTP connection. GET /v1/jobs/{id}/events is a reconnectable view of durable events, not a separate execution queue.

Settings activation is exclusive with admitted HTTP and public Python operations and active accepted source jobs. It validates a candidate configuration, including every retained kind in declared collections, and prepares replacement ports inside that exclusive gate. Service constructors require the owner's canonical ports, so preparation temporarily binds the candidate on the owner thread and restores the previous binding in finally before any new admission. On success it bumps policy_epoch for a changed configuration, atomically replaces workspace.toml, then swaps the in-memory owner/store/service binding. This swap is the commit point: later old-bridge cleanup failure or request cancellation cannot report an activation failure. Failed old bridges remain owned for bounded retry and host teardown, with sanitized diagnostics. Invalid or incompatible candidates preserve the previous effective configuration; busy work receives WORKSPACE_BUSY. A crash before file replacement may leave an extra epoch invalidation but keeps the old settings file. A crash after replacement recovers the new file on restart; file replacement and the database epoch are not one cross-store transaction. Existing index generations are guarded by representation semantics (embedding settings other than credentials, index backend/settings, and extraction identity), while credential references and reranker edits may change without reinterpreting those generations. The broader index_fingerprint remains unchanged for persisted family identity. No provider call or download occurs during activation.

The CLI, Python and MCP surfaces use the same runtime gate. MCP sessions consume the current service binding and close before the host drains accepted source jobs.