HTTP API · 8. Admin and operations

Forty-two registrations are for the operator rather than the agent: probes, version and feature reporting, the routing and shard view, the admin action queue, replication, repository scanning, and the two accounting surfaces.

Planes covered: Health, liveness and version · Routing, shards and GPUs · Admin and operations · Repository registry and code map · Workspace scan and storyline · Cost lens · Quota and credit meter.

Read chapter 0 first for the auth model, request-field notation and error shape.

Three of the five non-/v1 paths live here: /healthz, /readyz and /metrics. They are Public in every auth mode. Everything else on this chapter's planes needs admin:read or admin:write except the cost lens and the quota read.

8.1 Health, liveness and version

Six registrations, all Public.

MethodPathHandlerAuthRequestSuccess responseErrorsGateR/W
GET/healthzhealth.rs:23 healthzpublic · Public-Node health status-,R
GET/readyzhealth.rs:144 readyzpublic · Public-ok (bool)503-R
GET/metricshealth.rs:349 metricspublic · Public-Prometheus exposition text500-R
GET/v1/versionhealth.rs:491 get_versionpublic · Public-Build version and feature-flag state. Keys include activity_log, agent_workbench, ahead_by, api_key_configured, auto_capture, background_sync_enabled, behind, behind_by, checkout_ahead_by, checkout_behind_by, cloud_access, cloud_only_active, cloud_only_entitled, comparison_stale, configured, consolidation_scheduler, context_surface, contract_path, degraded, degraded_reason, embeddings, enabled, features, graph_expand-reports the state of auto_capture, consolidation_scheduler, context_surface, coord, local_ingest and mcpR
GET/v1/witness/smokewitness.rs:19 get_witness_smokepublic · Public-Witness and timestamp-authority configuration smoke result503-R
GET/v1/openapi.jsonopenapi.rs:499 openapi_jsonpublic · Public-OpenAPI document-,R

GET /v1/version is public and it reports your feature posture. Anyone who can reach the daemon can read which flags are on, whether the node is degraded and why, and whether it is ahead of or behind its sync peer. That is deliberate, clients need it to negotiate behaviour, and it is worth knowing before you expose the port.

/readyz returning 503 is the readiness gate, and it checks more than process liveness. A full data partition trips it: the daemon enforces a free-capacity floor on its data directory, and when that floor is breached /readyz fails while /healthz still passes. If integration tests start failing en masse with a bare "not healthy" message, check disk free space before anything else.

/v1/openapi.json is public and complete on paths, thin on schemas, see chapter 0 §0.13.

8.2 Routing, shards and GPUs

Six registrations, all admin:read.

MethodPathHandlerAuthRequestSuccess responseErrorsGateR/W
GET/v1/routerouting.rs:98 route_v1admin:read · AdminReadquery: tenant_id (String, req), stream_type (String, req), stream_id (String, req)stream_hash, shard_id, epoch, shard_map_version, leader_grpc_addr, leader_node_id, shard_gpu_id, owner_gpu_id, worker_up, shard_hosted400, 500-R
GET/v1/routing/routerouting.rs:60 route_debugadmin:read · AdminReadquery: tenant_id (String, req), stream_type (String, req), stream_id (String, req)stream_hash, shard_id, epoch, shard_map_version, leader_grpc_addr400, 500-R
GET/v1/routing/statusrouting.rs:260 routing_statusadmin:read · AdminRead-routing_table_version, last_reload_at, reload_errors, shards_loaded-,R
GET/v1/shardsrouting.rs:154 get_shardsadmin:read · AdminRead-node_id, shard_map_version, default_gpu_id, shards-,R
GET/v1/shard-mapadmin.rs:1641 get_shard_mapadmin:read · AdminRead-shard_map, current_version, blake3-,R
GET/v1/gpusrouting.rs:240 get_gpusadmin:read · AdminRead-GPU inventory-,R

/v1/route and /v1/routing/route answer the same question with different detail. /v1/route adds the GPU and worker-liveness view; /v1/routing/route is the minimal debug form. reload_errors on /v1/routing/status is the first field to read when routing looks stale.

8.3 Admin and operations

Fifteen registrations.

MethodPathHandlerAuthRequestSuccess responseErrorsGateR/W
POST/v1/admin/actionsadmin.rs:1472 post_admin_actionadmin:write · AdminWritebody: action_type (String, req), action_id (String, opt), actor (String, opt), reason (String, opt), params (JSON, opt)accepted (bool), action (AdminActionRecord)400, 500, 503CORECRUXD_ADMIN_FORCE_SEAL for the force-seal kind, default off; queue bounded by CORECRUXD_OPERATOR_ACTION_MAX_PENDING and _TIMEOUT_SECSW · control event
GET/v1/admin/actions/{actionId}admin.rs:1624 get_admin_actionadmin:read · AdminReadpath actionIdAction record404-R
POST/v1/admin/appendappend.rs:36 post_admin_appendadmin:write · AdminWritebody: tenant_id (String, req), stream_type (String, req), stream_id (String, req), expected_next_seq (u64, dflt), events (Vec AppendEventBody, req)keys appended, stream_id40064 MiB ingress body limitW
GET/v1/admin/controladmin.rs:1685 get_controladmin:read · AdminRead-Control-plane state-,R
GET/v1/admin/ops-logadmin.rs:1728 get_ops_logadmin:read · AdminReadquery: node_id (String, opt), since (String, opt), until (String, opt), from_seq (u64, opt), max_events (u32, opt)node_id, events, plus decodeError on a malformed entry412, 500-R
POST/v1/admin/valvesadmin.rs:1847 post_valvesadmin:write · AdminWritebody: actor (String, req), reason (String, req), pause_ingest (bool, opt), pause_compaction (bool, opt), throttle (SetThrottle, opt), read_only (bool, opt), emergency_brake (bool, opt)keys ok400, 500-W · control event
POST/v1/admin/shard-mapadmin.rs:1669 post_shard_mapadmin:write · AdminWriteShard-map updateUpdated map501-W
POST/v1/admin/stream-metaadmin.rs:2173 post_stream_metaadmin:write · AdminWritebody: tenant_id (String, req), stream_type (String, req), stream_id (String, req), actor (String, req), reason (String, req), min_live_seq (u64, opt), tombstone_seq (u64, opt)shard_map, current_version, blake3400, 501, 503-W
GET/v1/admin/replication/statusadmin.rs:2242 get_replication_statusadmin:read · AdminRead-min_live_seq, tombstone_seq-,R
GET/v1/admin/segments/fingerprintsadmin.rs:2400 get_segment_fingerprintsadmin:read · AdminRead-keys status, hash, schema, contract, segments, cold_segments, warm_segments, hot_docs, warm_docs, total_docs, hot_bytes, warm_bytes, hot_budget_bytes, tier_stats, calibration, cpu_only, embedding_fingerprint, fingerprint_guard, semantic_profile, semantic_profile_id, vector_metadata, warnings-,R
GET/v1/admin/sharing/postureadmin.rs:2464 get_sharing_postureadmin:read · AdminRead-keys configured, policy, sync, prefix, by_prefix, facts, private, private_count, pushable, pushable_count, total_versions_in_journal, would_be_private_after_backfill, note-reports CORECRUXD_SYNC_REMOTE_URLR
POST/v1/admin/sharing/backfilladmin.rs:2554 post_sharing_backfillall-of admin:read and facts:write · AdminWritebody: confirm (bool, dflt)keys mode, count, prefix, by_prefix, re_stored_count, would_re_store, note-,W · receipt, facts
POST/v1/admin/restartadmin.rs:2645 post_restart_daemonadmin:write · AdminWrite-keys status, note-,W
GET/v1/admin/versionhealth.rs:597 get_admin_versionadmin:read · AdminRead-keys product, commit, msrv, alg, mode_switching_supported, local_daemon_required_for_current_mode, action_enrichment, agent_workbench, api_key_configured, background_sync_enabled, cloud, cloud_access, cloud_only_active, cloud_only_entitled, configured, contract_path, degraded, degraded_reason, embeddings, features, gpu1_compute, graph_expand, mcp, passport-,R · receipt
POST/v1/internal/replication/segmentsadmin.rs:2048 post_replication_segmentreplication:write · InternalReplicationbody: shard_id (String, req), epoch (u64, req), segment_base64 (String, req), leader_node_id (String, opt), segment_hash (String, opt)shard_map, current_version, blake3; on mismatch keys code, expectedSegmentHash, actualSegmentHash, requestEpoch, routingEpoch, shardId400, 412, 413, 501-W

Reading this plane at 3am

POST /v1/admin/actions is a queue, not an execution. It returns accepted and an action record; 503 means the queue is full or the daemon is not accepting operator actions. Poll GET /v1/admin/actions/{actionId} for the outcome. The queue depth and per-action timeout are bounded by CORECRUXD_OPERATOR_ACTION_MAX_PENDING and CORECRUXD_OPERATOR_ACTION_TIMEOUT_SECS.

POST /v1/admin/valves is the emergency control. pause_ingest, pause_compaction, read_only and emergency_brake are the levers, and actor and reason are required, a valve change without an attributable reason is not accepted. It emits a control event, so the change is on the record.

compact-facts only does something if retention is configured. CORECRUXD_RETENTION_DAYS unset means retention is off, and the action then scrubs only facts that were already soft-deleted (mod.rs:374).

412 on /v1/internal/replication/segments is an epoch mismatch: the response carries requestEpoch and routingEpoch so you can see the gap. 400 with expectedSegmentHash and actualSegmentHash is a content mismatch. Neither is retryable unchanged.

POST /v1/admin/sharing/backfill needs two scopes, admin:read and facts:write (admin.rs:2559), and takes confirm. Run it with confirm: false first and read would_re_store against count. Check GET /v1/admin/sharing/posture before and after: would_be_private_after_backfill is the number the backfill is trying to move.

/v1/append and /v1/admin/append are the same handler in different auth classes. /v1/append is Write and accepts facts:write or admin:write; /v1/admin/append is AdminWrite and accepts admin:write only (mod.rs:643). Both carry the raised 64 MiB ingress body limit.

A poisoned credit-meter mutex fails every metered request closed with 500 until restart (mod.rs:308). That is deliberate, the alternative is an untracked debit or compute without a debit, but if metered routes start returning 500 uniformly and nothing else is wrong, restart is the fix.

8.4 Repository registry and code map

Seven registrations, all admin:read or admin:write, all tenant-bound.

MethodPathHandlerAuthRequestSuccess responseErrorsGateR/W
GET/v1/reposrepos.rs:571 get_reposadmin:read, tenant-bound · AdminReadquery: tenant_id (String, req)keys repos-,R
POST/v1/reposrepos.rs:117 post_repoadmin:write, tenant-bound · AdminWritebody: tenant_id (String, req), repo_id (String, dflt), root_path (String, dflt), clone_url (String, dflt), languages (Vec String, dflt), scan_mode (String, dflt)keys repo, note400, 404, 500-W · facts
GET/v1/repos/dependentsrepos.rs:606 get_repo_dependentsadmin:read, tenant-bound · AdminReadquery: tenant_id (String, req), ecosystem (String, req), name (String, req), cursor (String, dflt), limit (usize, dflt)keys dependents, ecosystem, name, kind, repo_id, package_known, source_manifest, version_locked, version_req, next_cursor, tenant_id400, 500-R
GET/v1/repos/scan-jobs/{job_id}repos.rs:586 get_repo_scan_jobadmin:read, tenant-bound · AdminReadpath job_id; query: tenant_id (String, req)Scan-job status404-R
GET/v1/repos/{repo_id}repos.rs:728 get_repoadmin:read, tenant-bound · AdminReadpath repo_id; query: tenant_id (String, req)keys repo404-R
DELETE/v1/repos/{repo_id}repos.rs:855 delete_repoadmin:write, tenant-bound · AdminWritepath repo_id; query: tenant_id (String, req)Deletion result-,W · facts
GET/v1/repos/{repo_id}/codemaprepos.rs:760 get_repo_codemapadmin:read, tenant-bound · AdminReadpath repo_id; query: tenant_id (String, req), format (String, dflt)keys repo_id, name, root_path, rel_path, languages, crates, internal_deps, file_count, total_loc, scan, scan_id, stats, started_at_unix_ms, duration_ms, hint, tenant_id400, 404, 500-R

tenant_id is required on every route here, including the deletes, and the scope check is tenant-bound. dependents and scan-jobs are static segments registered before {repo_id}.

POST /v1/repos registers a repository by root_path on the daemon's filesystem or by clone_url. A note in the response tells you which mode was taken.

8.5 Workspace scan and storyline

Four registrations.

MethodPathHandlerAuthRequestSuccess responseErrorsGateR/W
GET/v1/workspace/scanworkspace.rs:106 get_scanadmin:read · Read-Last scan result404-R
POST/v1/workspace/scanworkspace.rs:22 post_scanadmin:read in the handler · Write-keys scan_id, root_path, stats, duration_ms412, 500-W · receipt, facts
GET/v1/workspace/storylineworkspace.rs:153 get_storylineadmin:read · Readquery: root (String, dflt), format (String, dflt), include_tests (String, dflt)Storyline404-R
GET/v1/mcp/toolsworkspace.rs:80 get_mcp_toolsadmin:read · Read-keys tools, name, description, inputSchema, count-,R

The scope correction. The repository's older docs/api-reference.md documents POST /v1/workspace/scan as requiring admin:read. It is a POST and the route-auth contract classes it Write, accepting admin:write, facts:write or integrations:install. The handler does check only admin:read, so under the default shadow mode a read token performs a filesystem scan that writes facts and a receipt. Under enforce, a write-class scope is required. Provision for the write.

412 on the scan means a precondition failed, most often that no workspace root is configured.

GET /v1/mcp/tools lists the MCP tool catalogue over HTTP without going through the MCP listener. It is the cheapest way to check what an agent's tools/list would return.

8.6 Cost lens

Two registrations. Cost here is a reported, supporting field, not a billing record and not a meter. The daemon stores what a client tells it and hands it back.

MethodPathHandlerAuthRequestSuccess responseErrorsGateR/W
GET/v1/cost/reportcost.rs:111 get_cost_reportany-of facts:read, admin:read, tenant-bound · Readquery parameters as a string map, including tenant_id, session_id, token_budgetkeys report, has_report, sessions, session_id, tenant_id, token_budget400-R
POST/v1/cost/reportcost.rs:54 post_cost_reportany-of facts:write, admin:write, tenant-bound · Writebody: tenant_id (String, req), report (CostReport, req), session_id (String, dflt)keys stored, session_id, received_at400-W

8.7 Quota and credit meter

Two registrations, both gated off by default.

MethodPathHandlerAuthRequestSuccess responseErrorsGateR/W
GET/v1/quotaquota.rs:134 get_quotaany-of query:read, admin:read · FeatureGated-keys passport, surface, surfaces, limit, remaining, refill_per_minute, hosted_surfaces401, 404CORECRUXD_QUOTA, default offR
POST/v1/credits/spendcredit_meter.rs:22 post_credit_spendadmin:write · FeatureGatedbody: quote (PinnedCreditQuote, req)keys spend, spend_receipt, credit_spend_receipt, reservation404, 409, 500CORECRUXD_CREDIT_METER, default offW · receipt

The quota middleware is a separate thing from GET /v1/quota. The middleware (layer 7 in chapter 0 §0.7) is a pass-through unless CORECRUXD_QUOTA=1 and the request path prefix matches CORECRUXD_QUOTA_HOSTED_SURFACES. That second variable is empty by default, so even with CORECRUXD_QUOTA=1 every surface counts as local compute and is unlimited until you name the hosted prefixes.

A quota 429 never spends credit. The bucket sits before any metered execution and the inner handler is never reached. If you see a 429 with quota headers, nothing was debited.

409 on /v1/credits/spend means the quote is no longer valid, re-quote rather than retry. 404 means the flag is off.

With CORECRUXD_CREDIT_METER off, the metered paths keep their legacy no-burn shape rather than failing. A 200 from a metered route on a daemon with the meter off does not mean credit was checked.

8.8 Failure modes on this plane

SymptomMost likely cause
Every integration test fails with a bare "not healthy" and empty stderr/readyz is failing its data-directory free-capacity floor. Check disk free space first.
503 from POST /v1/admin/actionsThe operator-action queue is full or the daemon is not accepting actions. Check CORECRUXD_OPERATOR_ACTION_MAX_PENDING.
400 on POST /v1/admin/valves with all levers setactor and reason are required. A valve change must be attributable.
compact-facts reports nothing removedCORECRUXD_RETENTION_DAYS is unset, so retention is off and only already-soft-deleted facts are scrubbed.
412 on /v1/internal/replication/segmentsEpoch mismatch. Compare requestEpoch and routingEpoch in the body.
400 on the same route with two hashes in the bodySegment content mismatch. Not retryable unchanged.
Every metered route returning 500 and nothing else wrongPoisoned credit-meter mutex. It fails closed until restart, deliberately.
A read token running POST /v1/workspace/scanExpected under the default shadow mode: the handler checks admin:read. The contract class is Write. Set CORECRUXD_ROUTE_AUTH=enforce.
403 on POST /v1/admin/sharing/backfill with admin:write aloneIt needs admin:read and facts:write.
CORECRUXD_QUOTA=1 set but nothing is ever limitedCORECRUXD_QUOTA_HOSTED_SURFACES is empty, so no path prefix is in scope.
404 on /v1/quota or /v1/credits/spendThe respective flag is off. Both are off by default.
/v1/version disclosing your flag posture to an unauthenticated callerExpected. It is Public by contract.

Sources