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.
| Method | Path | Handler | Auth | Request | Success response | Errors | Gate | R/W |
|---|---|---|---|---|---|---|---|---|
GET | /healthz | health.rs:23 healthz | public · Public | - | Node health status | - | , | R |
GET | /readyz | health.rs:144 readyz | public · Public | - | ok (bool) | 503 | - | R |
GET | /metrics | health.rs:349 metrics | public · Public | - | Prometheus exposition text | 500 | - | R |
GET | /v1/version | health.rs:491 get_version | public · 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 mcp | R |
GET | /v1/witness/smoke | witness.rs:19 get_witness_smoke | public · Public | - | Witness and timestamp-authority configuration smoke result | 503 | - | R |
GET | /v1/openapi.json | openapi.rs:499 openapi_json | public · 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.
| Method | Path | Handler | Auth | Request | Success response | Errors | Gate | R/W |
|---|---|---|---|---|---|---|---|---|
GET | /v1/route | routing.rs:98 route_v1 | admin:read · AdminRead | query: 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_hosted | 400, 500 | - | R |
GET | /v1/routing/route | routing.rs:60 route_debug | admin:read · AdminRead | query: tenant_id (String, req), stream_type (String, req), stream_id (String, req) | stream_hash, shard_id, epoch, shard_map_version, leader_grpc_addr | 400, 500 | - | R |
GET | /v1/routing/status | routing.rs:260 routing_status | admin:read · AdminRead | - | routing_table_version, last_reload_at, reload_errors, shards_loaded | - | , | R |
GET | /v1/shards | routing.rs:154 get_shards | admin:read · AdminRead | - | node_id, shard_map_version, default_gpu_id, shards | - | , | R |
GET | /v1/shard-map | admin.rs:1641 get_shard_map | admin:read · AdminRead | - | shard_map, current_version, blake3 | - | , | R |
GET | /v1/gpus | routing.rs:240 get_gpus | admin: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.
| Method | Path | Handler | Auth | Request | Success response | Errors | Gate | R/W |
|---|---|---|---|---|---|---|---|---|
POST | /v1/admin/actions | admin.rs:1472 post_admin_action | admin:write · AdminWrite | body: action_type (String, req), action_id (String, opt), actor (String, opt), reason (String, opt), params (JSON, opt) | accepted (bool), action (AdminActionRecord) | 400, 500, 503 | CORECRUXD_ADMIN_FORCE_SEAL for the force-seal kind, default off; queue bounded by CORECRUXD_OPERATOR_ACTION_MAX_PENDING and _TIMEOUT_SECS | W · control event |
GET | /v1/admin/actions/{actionId} | admin.rs:1624 get_admin_action | admin:read · AdminRead | path actionId | Action record | 404 | - | R |
POST | /v1/admin/append | append.rs:36 post_admin_append | admin:write · AdminWrite | body: tenant_id (String, req), stream_type (String, req), stream_id (String, req), expected_next_seq (u64, dflt), events (Vec AppendEventBody, req) | keys appended, stream_id | 400 | 64 MiB ingress body limit | W |
GET | /v1/admin/control | admin.rs:1685 get_control | admin:read · AdminRead | - | Control-plane state | - | , | R |
GET | /v1/admin/ops-log | admin.rs:1728 get_ops_log | admin:read · AdminRead | query: 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 entry | 412, 500 | - | R |
POST | /v1/admin/valves | admin.rs:1847 post_valves | admin:write · AdminWrite | body: 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 ok | 400, 500 | - | W · control event |
POST | /v1/admin/shard-map | admin.rs:1669 post_shard_map | admin:write · AdminWrite | Shard-map update | Updated map | 501 | - | W |
POST | /v1/admin/stream-meta | admin.rs:2173 post_stream_meta | admin:write · AdminWrite | body: 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, blake3 | 400, 501, 503 | - | W |
GET | /v1/admin/replication/status | admin.rs:2242 get_replication_status | admin:read · AdminRead | - | min_live_seq, tombstone_seq | - | , | R |
GET | /v1/admin/segments/fingerprints | admin.rs:2400 get_segment_fingerprints | admin: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/posture | admin.rs:2464 get_sharing_posture | admin: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_URL | R |
POST | /v1/admin/sharing/backfill | admin.rs:2554 post_sharing_backfill | all-of admin:read and facts:write · AdminWrite | body: confirm (bool, dflt) | keys mode, count, prefix, by_prefix, re_stored_count, would_re_store, note | - | , | W · receipt, facts |
POST | /v1/admin/restart | admin.rs:2645 post_restart_daemon | admin:write · AdminWrite | - | keys status, note | - | , | W |
GET | /v1/admin/version | health.rs:597 get_admin_version | admin: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/segments | admin.rs:2048 post_replication_segment | replication:write · InternalReplication | body: 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, shardId | 400, 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.
| Method | Path | Handler | Auth | Request | Success response | Errors | Gate | R/W |
|---|---|---|---|---|---|---|---|---|
GET | /v1/repos | repos.rs:571 get_repos | admin:read, tenant-bound · AdminRead | query: tenant_id (String, req) | keys repos | - | , | R |
POST | /v1/repos | repos.rs:117 post_repo | admin:write, tenant-bound · AdminWrite | body: 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, note | 400, 404, 500 | - | W · facts |
GET | /v1/repos/dependents | repos.rs:606 get_repo_dependents | admin:read, tenant-bound · AdminRead | query: 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_id | 400, 500 | - | R |
GET | /v1/repos/scan-jobs/{job_id} | repos.rs:586 get_repo_scan_job | admin:read, tenant-bound · AdminRead | path job_id; query: tenant_id (String, req) | Scan-job status | 404 | - | R |
GET | /v1/repos/{repo_id} | repos.rs:728 get_repo | admin:read, tenant-bound · AdminRead | path repo_id; query: tenant_id (String, req) | keys repo | 404 | - | R |
DELETE | /v1/repos/{repo_id} | repos.rs:855 delete_repo | admin:write, tenant-bound · AdminWrite | path repo_id; query: tenant_id (String, req) | Deletion result | - | , | W · facts |
GET | /v1/repos/{repo_id}/codemap | repos.rs:760 get_repo_codemap | admin:read, tenant-bound · AdminRead | path 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_id | 400, 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.
| Method | Path | Handler | Auth | Request | Success response | Errors | Gate | R/W |
|---|---|---|---|---|---|---|---|---|
GET | /v1/workspace/scan | workspace.rs:106 get_scan | admin:read · Read | - | Last scan result | 404 | - | R |
POST | /v1/workspace/scan | workspace.rs:22 post_scan | admin:read in the handler · Write | - | keys scan_id, root_path, stats, duration_ms | 412, 500 | - | W · receipt, facts |
GET | /v1/workspace/storyline | workspace.rs:153 get_storyline | admin:read · Read | query: root (String, dflt), format (String, dflt), include_tests (String, dflt) | Storyline | 404 | - | R |
GET | /v1/mcp/tools | workspace.rs:80 get_mcp_tools | admin: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.
| Method | Path | Handler | Auth | Request | Success response | Errors | Gate | R/W |
|---|---|---|---|---|---|---|---|---|
GET | /v1/cost/report | cost.rs:111 get_cost_report | any-of facts:read, admin:read, tenant-bound · Read | query parameters as a string map, including tenant_id, session_id, token_budget | keys report, has_report, sessions, session_id, tenant_id, token_budget | 400 | - | R |
POST | /v1/cost/report | cost.rs:54 post_cost_report | any-of facts:write, admin:write, tenant-bound · Write | body: tenant_id (String, req), report (CostReport, req), session_id (String, dflt) | keys stored, session_id, received_at | 400 | - | W |
8.7 Quota and credit meter
Two registrations, both gated off by default.
| Method | Path | Handler | Auth | Request | Success response | Errors | Gate | R/W |
|---|---|---|---|---|---|---|---|---|
GET | /v1/quota | quota.rs:134 get_quota | any-of query:read, admin:read · FeatureGated | - | keys passport, surface, surfaces, limit, remaining, refill_per_minute, hosted_surfaces | 401, 404 | CORECRUXD_QUOTA, default off | R |
POST | /v1/credits/spend | credit_meter.rs:22 post_credit_spend | admin:write · FeatureGated | body: quote (PinnedCreditQuote, req) | keys spend, spend_receipt, credit_spend_receipt, reservation | 404, 409, 500 | CORECRUXD_CREDIT_METER, default off | W · 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
| Symptom | Most 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/actions | The 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 set | actor and reason are required. A valve change must be attributable. |
compact-facts reports nothing removed | CORECRUXD_RETENTION_DAYS is unset, so retention is off and only already-soft-deleted facts are scrubbed. |
412 on /v1/internal/replication/segments | Epoch mismatch. Compare requestEpoch and routingEpoch in the body. |
400 on the same route with two hashes in the body | Segment content mismatch. Not retryable unchanged. |
Every metered route returning 500 and nothing else wrong | Poisoned credit-meter mutex. It fails closed until restart, deliberately. |
A read token running POST /v1/workspace/scan | Expected 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 alone | It needs admin:read and facts:write. |
CORECRUXD_QUOTA=1 set but nothing is ever limited | CORECRUXD_QUOTA_HOSTED_SURFACES is empty, so no path prefix is in scope. |
404 on /v1/quota or /v1/credits/spend | The respective flag is off. Both are off by default. |
/v1/version disclosing your flag posture to an unauthenticated caller | Expected. It is Public by contract. |
Sources
- crates/corecruxd/src/http/health.rs:144,
readyz - crates/corecruxd/src/http/health.rs:598,
admin:readon the admin version route - crates/corecruxd/src/http/route_auth.rs:77, the
Publicset - crates/corecruxd/src/http/route_auth.rs:98,
InternalReplication - crates/corecruxd/src/http/admin.rs:2559, sharing backfill, all-of
admin:readandfacts:write - crates/corecruxd/src/http/append.rs:41, append
admin:write - crates/corecruxd/src/http/mod.rs:643, one handler, two auth classes
- crates/corecruxd/src/http/mod.rs:308, credit-meter mutex poisoning fails closed
- crates/corecruxd/src/http/mod.rs:374,
CORECRUXD_RETENTION_DAYS - crates/corecruxd/src/http/repos.rs:126, repo write, tenant-bound
- crates/corecruxd/src/http/repos.rs:577, repo read, tenant-bound
- crates/corecruxd/src/http/cost.rs:31, cost read and write scope sets
- crates/corecruxd/src/http/quota.rs:134, quota read
- crates/corecruxd/src/http/credit_meter.rs:33, credit spend scope
- crates/corecruxd/src/config.rs:1362,
CORECRUXD_QUOTAdefault - crates/corecruxd/src/config.rs:1373,
CORECRUXD_CREDIT_METERdefault

