API Reference

August 22, 2026 · View on GitHub

Crux Daemon exposes three network surfaces by default:

  • HTTP API on 14800
  • gRPC API on 4007
  • Built-in MCP server on 14801 when CORECRUXD_MCP_ENABLED=true (default)

HTTP Endpoints

Infrastructure

MethodPathDescriptionAuth Scope
GET/healthzHealth check with build metadataNone
GET/readyzReadiness probe (lock held, routing loaded, capacity OK)None
GET/metricsPrometheus metricsNone
GET/v1/versionBuild version, feature flags, sync posture, and cached git update status (current, behind, ahead, diverged, disabled, unavailable, or error)None

Query & Retrieval

MethodPathDescriptionAuth Scope
POST/v1/query/text-searchBM25 text search with token budget and coverage scoringquery:read
POST/v1/query/text-search/expandProgressive retrieval — expand scan results with full contentquery:read
POST/v1/query/graph-expandGraph traversal from seed artifacts with budgetquery:read *
POST/v1/query/time-rangeTemporal range query over artifact state changesquery:read *

* Requires a dataplane-enabled deployment. Returns 501 in Crux Daemon.

Segment coordinates on a result

Each /v1/query/text-search result (and each /expand chunk) carries two segment coordinates, which are not the same number:

FieldMeaning
segment_indexPosition in the loaded-reader list, ascending by sequence. 0-based, global, and mutable — see the warning below. Pairs with doc_id to form result_id, and is the value /expand takes back.
segment_seqThe sealed segment's own sequence — the segment_seq the /v1/local/ingest receipt returned. 1-based and stable for the life of the segment.

To join a query result back to the ingest that produced it, join on segment_seq.

Never derive one from the other. segment_index is a position in the daemon's loaded-reader list, so it moves when any segment is loaded, sealed or erased — including segments belonging to other tenants, written by someone else, while your query runs. Measured on one host on 2026-08-07: segment_seq - segment_index was 1 in the morning, 18 that afternoon and 17 minutes later, uniformly across every tenant. There is no offset to store, not even a freshly measured one.

A consumer that translates between them scores a plausible, uniform 0% instead of erroring, because every hit unmaps at once and nothing raises. That is how the BEAM-100K mapping was wrong for a full run before anyone noticed. If you are on a daemon old enough not to return segment_seq, do not guess the offset: discover the tenant's live segment ids through the same query path you score with, and pair them in ascending order with your ingest batches — segment sequences are allocated monotonically, so ascending live order matches ingest order whatever the absolute numbers have become.

Local prose ingest

MethodPathDescriptionAuth Scope
POST/v1/local/ingestSeal pre-chunked prose into a local segment (BM25 + optional dense)admin:write (tenant-scoped)

The 202 response reports the dense lane explicitly, because a corpus that failed to embed still indexes over BM25 and otherwise looks healthy:

FieldMeaning
dense_vectorsVectors actually persisted to the segment's .ccxv companion
dense_expectedVectors this ingest expected — every chunk when the node embeds server-side, the caller-vectored subset otherwise
dense_statusok, partial, skipped, not_configured (no vectors expected — BM25-only by configuration), or not_applicable (idempotent re-ingest, nothing sealed)

Assert dense_status == "ok" (or dense_vectors == dense_expected) after every ingest that expects semantic recall. skipped means the embed step failed and the segment sealed lexical-only; the daemon logs local-ingest-dense-gap-sealed at WARN with the segment sequence, and the cause in the preceding local-ingest-embedding-failed line. Both fields are additive — a client that ignores them sees the response shape it saw before (local_ingest.rs).

Fact Store

MethodPathDescriptionAuth Scope
PUT/v1/factsStore or update a shared fact (entity + key + value + confidence)query:read
PUT/v1/facts/bulkBulk-store multiple factsquery:read
GET/v1/factsQuery facts by text with token budgetquery:read
GET/v1/facts/{factId}Retrieve a specific fact by IDquery:read
DELETE/v1/facts/{factId}Delete a factquery:read
GET/v1/facts/entity/{entity}List all facts for an entityquery:read

HTTP fact writes do not support private=true. Private facts and per-agent visibility are MCP-only features.

Work and Orchestrators

Work and orchestrator records are authority-sensitive, tenant-scoped surfaces. In JWT modes, creator/updater/commenter identity and tenant come from verified claims; matching body fields are compatibility constraints, not an impersonation mechanism. A caller cannot list, read, mutate, comment on, attach members to, or resolve gates for another tenant.

In local off/dev_scopes mode, an explicit passport header or matching body assertion is recorded as operator:unverified:<id>. It is not a verified human identity: work state changes always queue for review. Human gate decisions in authenticated modes require facts:write, a canonical JWT passport_id, and the work tenant; MCP agent tokens and sub-only JWTs cannot approve or reject. An unmapped MCP agent token is attributed as agent:<token-name> and is gated as automation; only an explicit CRUX_AGENT_PASSPORTS mapping may resolve it to a real passport id.

The generic /v1/entities/{kind}/{id} and MCP entity_* APIs reject governed orchestrator records and omit them from unfiltered listings. Use the typed /v1/orchestrators routes so tenant and actor checks cannot be bypassed.

Session Store

MethodPathDescriptionAuth Scope
PUT/v1/sessions/{sessionId}/stateStore session state (JSON blob)query:read
GET/v1/sessions/{sessionId}/stateRetrieve session statequery:read

Event Append

MethodPathDescriptionAuth Scope
POST/v1/admin/appendAppend events to a stream (/v1/append compatibility alias)admin:write *

* Requires a dataplane-enabled deployment. Returns 501 in Crux Daemon.

CROWN Receipts

MethodPathDescriptionAuth Scope
GET/v1/receipts/{receiptId}Retrieve a CROWN receipt bodyevents:read
GET/v1/receipts/{receiptId}/signatureRetrieve receipt Ed25519 signatureevents:read
GET/v1/receipts/{receiptId}/verificationVerify receipt signature and chainevents:read

Credits

MethodPathDescriptionAuth Scope
POST/v1/credits/spendDefault-off comped-wallet spend rail. Requires CORECRUXD_CREDIT_METER=1; consumes a pinned quote, reserves/spends seeded credits idempotently, and returns a signed crux.credit_spend_receipt.v1. Does not mint fiat credits or call Paddle.admin:write

When CORECRUXD_CREDIT_METER=1, successful RCX-verified POST /v1/gpu1/rerank calls also reserve and spend 3 comped-wallet credits. The pinned crux.credit_quote.v1 rides at options.credit_quote; the crux.gpu1.compute_response.v1 envelope adds credit_spend_receipt, credits_spent, and wallet_balance. Failed/degraded compute releases the reservation and emits no spend stamp.

BYOK Provenance (default off)

MethodPathDescriptionAuth Scope
POST/v1/provenance/signSign an asset with a request-scoped caller P-256 key and leaf-first certificate chainprovenance:write or admin:write
POST/v1/provenance/verifyVerify envelope integrity and optional asset binding without retaining a recordprovenance:write or admin:write
POST/v1/provenance/verify-recordVerify and retain a passport-signed record; supports Idempotency-Keyprovenance:write or admin:write

Set CORECRUXD_FEATURE_PROVENANCE_API=1 to mount the routes. They require an explicit authorized tenant and a safe transport posture; see the BYOK provenance quickstart. Exact leaf pins can establish a narrow operator-selected identity policy, but the beta does not perform CA-chain/root validation. Metering remains a no-op until the fractional-credit contract is ratified and implemented.

CORECRUXD_PROVENANCE_RETENTION_DAYS=1..3650 enables activity-driven retained-record expiry; unset means no automatic deletion. Sweeps preserve active tenant-wide or provenance::verification_record::-scoped legal holds and mint count-only governance receipts. A non-empty sweep reports its receipt status/id and deletion count in X-Cuecrux-Retention-* response headers. See the quickstart for the exact lifecycle and fail-closed behavior.

All three routes share a tenant-scoped 120-request/minute budget keyed by the verified stable JWT sub or passport_id, so token rotation and switching operations do not create fresh allowance. A rejection is 429 with Retry-After: 60. This sits behind the daemon-wide effective-client-IP token bucket, body limits, and concurrency/load-shed layer. The principal table is process-local: a multi-replica hosted deployment must additionally enforce a shared limit at its edge or gateway. Configure CORECRUXD_TRUSTED_PROXY_CIDRS before relying on forwarded client addresses; loopback is exempt by default.

Replay Exports

MethodPathDescriptionAuth Scope
GET/v1/replay/exports/receipts/{receiptId}Export receipt bundle (ZIP/TAR+ZST)events:read
GET/v1/replay/exports/answers/{answerId}Export answer bundleevents:read
GET/v1/replay/exports/actions/{actionId}Export action bundleevents:read
GET/v1/replay/exports/streams/{streamType}/{streamId}Export stream bundleevents:read

Self-Observation (crux-observe)

MethodPathDescriptionAuth Scope
GET/v1/ops/factsQuery operational factsadmin:read
GET/v1/ops/errorsQuery recent errors since timestampadmin:read
GET/v1/ops/healthOperational health summaryadmin:read
POST/v1/bootstrap/pullPull bootstrap facts with token budgetadmin:read
GET/v1/bootstrap/statusCheck bootstrap seeded stateadmin:read

Projections

MethodPathDescriptionAuth Scope
GET/v1/projections/entity/countEntity count by tenantquery:read *
GET/v1/projections/entity/timelineEntity state timelinequery:read *
GET/v1/projections/entity/current-stateCurrent entity statequery:read *
GET/v1/admin/projections/metaProjection cursor metadata per shardadmin:read *
POST/v1/admin/projections/rebuildTrigger projection rebuildadmin:write *
GET/v1/admin/projections/artifacts/{artifactId}/stateArtifact living stateadmin:read *
GET/v1/admin/projections/artifacts/{artifactId}/relationsArtifact relationsadmin:read *
GET/v1/admin/projections/artifacts/{artifactId}/dependentsArtifact dependentsadmin:read *
GET/v1/admin/projections/artifacts/{artifactId}/pressure-eventsArtifact pressure eventsadmin:read *

* Requires a dataplane-enabled deployment. Returns 501 in Crux Daemon.

Repos & Code Map

AST-derived code structure for registered repositories. Registration with a root_path runs a one-shot scan (Rust natively; TS/TSX/Vue/Python via tree-sitter) and persists it; the repo watch loop re-indexes on change. The codemap endpoint is the read side — the daemon serving its own code understanding back to agents.

MethodPathDescriptionAuth Scope
GET/v1/repos?tenant_id=…List registered repos for a tenantadmin:read
POST/v1/reposRegister a repo (root_path scans now; clone_url defers)admin:write
GET/v1/repos/{repoId}?tenant_id=…One registrationadmin:read
DELETE/v1/repos/{repoId}?tenant_id=…Unregister (stops watch)admin:write
GET/v1/repos/{repoId}/codemap?tenant_id=…&format=summary|fullAST code map: summary = stats + per-crate rollup; full = files, symbols, deps, routesadmin:read
POST/v1/workspace/scanScan the daemon's own workspace (CORECRUXD_WORKSPACE_PATH)admin:write
GET/v1/workspace/scanLatest self-scan in fulladmin:read
GET/v1/workspace/storyline?format=tree|jsonPer-route call trees from the self-scanadmin:read

Routing & Shards

MethodPathDescriptionAuth Scope
GET/v1/shardsList shards with store statusadmin:read
GET/v1/shard-mapCurrent shard map (shard → node assignment)admin:read
GET/v1/routeRoute a stream to its owning shardadmin:read
GET/v1/routing/routeDebug route resolutionadmin:read
GET/v1/routing/statusRouting table version and reload statusadmin:read
GET/v1/gpusGPU inventoryadmin:read *

* Requires a dataplane-enabled deployment. Returns 501 in Crux Daemon.

Admin & Operations

MethodPathDescriptionAuth Scope
POST/v1/admin/shard-mapUpdate shard mapadmin:write
GET/v1/admin/controlCurrent control stateadmin:read
POST/v1/admin/restartRequest daemon process restartadmin:write
GET/v1/admin/ops-logStructured operations logadmin:read
POST/v1/admin/valvesSet valve states (throttle, pause, emergency brake)admin:write
GET/v1/admin/replication/statusReplication topology statusadmin:read *
POST/v1/admin/actionsSubmit admin action (seal, scrub, verify, rebalance)admin:write
GET/v1/admin/actions/{actionId}Get admin action statusadmin:read
POST/v1/admin/stream-metaUpdate stream metadataadmin:write *
GET/v1/admin/tenants/{tenantId}/footprintSegments, docs and bytes a tenant occupies in the retrieval corpusadmin:read †
POST/v1/admin/forget-tenantsErase the named tenants' retrieval corpora (/v1/admin/forget-tenant is the singular alias)admin:write †
DELETE/v1/admin/forget-tenants/{tenantId}Lift a mask-only erasureadmin:write †
POST/v1/internal/replication/segmentsReceive replicated segmentsreplication:write *

* Requires a dataplane-enabled deployment. Returns 501 in Crux Daemon.

† Requires CORECRUXD_TENANT_ERASURE=1; the routes 404 while it is unset.

Tenant corpus erasure

POST /v1/admin/forget-tenants takes {"tenant_ids": ["…"], "reclaim": false}. Empty ids are dropped and duplicates collapsed in first-seen order; the batch is capped at 4096, __-prefixed (reserved) tenant ids are refused, and admin:write is checked for every named tenant before anything is mutated — a partially authorised batch is 403 and erases nothing.

Two layers:

  • Layer 1 (default). Segments sealed up to the current watermark_segment_seq become invisible to that tenant, and the mask is persisted to <data_dir>/forgotten-tenants.json before the response returns. Reversible via the DELETE route. Anything ingested afterwards is served normally, so a corpus can be erased and re-paved under the same tenant id.
  • Layer 2 ("reclaim": true). Additionally deletes the file group of every segment whose documents all belong to that tenant. Irreversible — recovery is restore-from-backup only. Segments shared with another tenant are never deleted; they stay masked and are reported as mixed_segments_retained.

Both routes re-read the shard directories from disk before answering, so a segment sealed since the last scan is inside the blast radius rather than silently outside it. Segments are found by their .ccxseg file, not by a companion: a segment holding fact records has no .ccxi to key off, and it is still erasable. Tenant membership comes from the .ccxi doc table where one exists and from the segment's own frame headers where it does not.

A segment that is on disk but cannot be read at all is reported as unattributable_segments on both routes, and is neither masked nor reclaimed — deleting a segment whose owner cannot be established risks a co-tenant's data. A non-zero count means an erasure is incomplete and needs an operator.

Scope is the retrieval corpus only. A tenant's facts, sessions and activity rows are untouched, so the response says corpus_erased, not tenant_forgotten. Each tenant's erasure mints a signed governance receipt carrying counts, the watermark and the scope — never document content.


gRPC Services

Default port: 4007. Proto files in proto/.

CoreCruxDataPlaneV1

All RPCs return UNIMPLEMENTED in Crux Daemon and require a dataplane-enabled deployment.

RPCRequestResponseDescription
AppendBatchAppendBatchRequestAppendBatchResponseAppend events with deduplication
ReadStreamReadStreamRequeststream ReadStreamResponseRead events from a stream
ReadStreamBatchedReadStreamBatchedRequeststream ReadStreamBatchResponseBatched read with configurable limits
ReadStreamBatchedUnaryReadStreamBatchedRequestReadStreamBatchResponseUnary batched read
ReadManyBatchedUnaryReadManyBatchedRequestReadManyBatchedResponseRead multiple streams in one call
ReadManyFramesBatchedUnaryReadManyFramesBatchedRequestReadManyFramesBatchedResponseRead raw frames from multiple streams
ReadFramesBatchedUnaryReadStreamBatchedRequestReadFramesBatchRawResponseRaw frame read
ReplaySessionstream ReplaySessionRequeststream ReplaySessionResponseBidirectional streaming replay
ReadFramesReadFramesRequeststream ReadFramesResponseStream raw frame bytes

CoreCruxExportV1

RPCRequestResponseDescription
ExportReceiptBundleExportReceiptBundleRequeststream ExportChunkStream large export bundles

Returns UNIMPLEMENTED in Crux Daemon.

CoreCruxObserveV1

RPCRequestResponseDescription
QueryOpsFactsQueryOpsFactsRequestQueryOpsFactsResponseQuery operational facts
QueryOpsErrorsQueryOpsErrorsRequestQueryOpsErrorsResponseQuery error log
GetOpsHealthGetOpsHealthRequestGetOpsHealthResponseHealth summary (JSON)
BootstrapPullBootstrapPullRequestBootstrapPullResponseBootstrap fact pull with token budget
GetBootstrapStatusGetBootstrapStatusRequestGetBootstrapStatusResponseSeeded state and fact count

MCP (JSON-RPC over HTTP)

Endpoint: GET/POST http://<host>:14801/mcp

  • GET /mcp returns server info and protocol metadata.
  • POST /mcp serves JSON-RPC 2.0 requests such as tools/list and tools/call.
  • If CRUX_AGENT_TOKEN or CRUX_AGENT_TOKENS is configured, MCP requests must include Authorization: Bearer <token>.
  • Accept: text/event-stream opens a Streamable HTTP SSE stream. SSE streams use the same bearer-token rule, validate Mcp-Session-Id, and are capped by CRUX_MCP_SSE_MAX_SESSIONS and CRUX_MCP_SSE_MAX_SESSIONS_PER_OWNER.
  • Private facts, agent-scoped sessions, and handoff workflows are available through MCP tools, not the HTTP /v1/facts surface.
  • sync_status tells agents whether the node is local-only, sync-enabled, or degraded before they attempt hosted-platform integration.
  • update_status tells agents whether the local checkout is current, behind, ahead, diverged, disabled, unavailable, or erroring before they propose an upgrade or restart.

See agent-guide.md and examples/mcp-configs/README.md for JSON-RPC examples and client configs.


Authentication

Configured via CORECRUXD_AUTH_MODE:

ModeDescriptionUse Case
offNo authenticationLocal development only
dev_scopesScopes parsed from header, no signature verificationDevelopment/testing
jwt_hs256JWT with HMAC-SHA256 signatureSimple production setups
jwt_jwksJWT with JWKS key rotationProduction with key management

Scopes are passed via Authorization: Bearer <token> header. Required scopes are listed per endpoint above. X-Corecrux-Passport-Id is only an unverified local assertion in off and dev_scopes; production authority must come from verified token claims.

HTTP fact tenant isolation (CORECRUXD_TENANT_WRITE_STAMP)

In jwt_hs256 and jwt_jwks modes, the default is on: affected HTTP fact-backed writes stamp the verified JWT tenant and reads filter to the same tenant. A token with one tenant needs no selector. On tenant-implicit routes, a token with multiple tenants or a wildcard tenant must send X-Corecrux-Tenant-Id; an explicit route/body tenant is itself a selector and must agree with that header when both are present. A missing tenant claim, ambiguous selection, mismatch, or unauthorized selection is rejected. The separately documented raw-admin fact reads remain intentionally cross-tenant. The policy is parsed once at startup, and an invalid value aborts startup.

off is a deliberate legacy migration override: reads and writes use the shared default tenant even when JWT claims differ. shadow preserves that same storage behaviour while logging requests that on would move or reject. Historical default rows are not migrated automatically.

This switch covers wired HTTP fact-backed surfaces, including generic and console facts, context recall, engram overlays, memory candidates, result envelopes, replay capsules, and their paired HTTP audit/export reads. It is not a universal daemon tenant switch: the MCP compatibility plane still uses default, while entity, edge, session, projection, and other control stores retain their own tenant contracts.

Route authorization gate (CORECRUXD_ROUTE_AUTH)

Independently of CORECRUXD_AUTH_MODE, the daemon runs a deny-by-default route authorization layer as HTTP middleware, in front of (and in addition to) the per-handler scope checks. It maps every routed request to a route contract — the accepted (any-of) scope set for that route template and method — and is controlled by CORECRUXD_ROUTE_AUTH (read once at startup):

ValueBehaviour
offPass-through; the middleware does nothing.
shadowEvaluates the contract and logs a structured route_auth_shadow_mismatch warning on any would-deny, but never blocks. It is the derived default only for auth-off, loopback-only operation; otherwise it is an explicit migration override.
enforcePublic routes (/healthz, /readyz, /metrics, /session, /invocation/verify, /v1/openapi.json, /v1/version, /v1/witness/smoke, and the /v1/auth/* bootstrap rails) pass with no auth headers. Every other route requires one of its contract scopes via the same primitive the handlers use. A route with no contract entry — or a request axum could not match to a route template — fails closed with 403.

With the variable unset, authentication enabled or a non-loopback listener selects enforce; only auth-off plus loopback derives shadow. An empty or unknown explicit value also selects enforce and emits a startup warning.

The gate authorizes scopes only; feature-flag gating for optional surfaces stays in the handler. When CORECRUXD_AUTH_MODE=off, the scope check is a no-op (there is nothing to enforce), but enforce still fails closed on uncontracted routes.


Error Format

All HTTP errors use RFC 7807 Problem Details:

{
  "type": "https://errors.cuecrux.com/bad-request",
  "title": "Bad Request",
  "status": 400,
  "detail": "query must not be empty"
}

See docs/error-catalogue.md for the full error code reference.