HTTP API · 0. Conventions and how to read this reference

The Crux Daemon serves 303 unique HTTP paths over 347 method-and-path registrations, all but five of them under /v1. This chapter is the contract they share: where they live, how they authenticate, what an error looks like, what limits apply before your request reaches a handler, and which chapter documents which routes.

This is reference material in the Diátaxis sense. It enumerates; it does not teach. If you are building your first integration, start with the daemon developer guide and come back here to look things up.

Every route table in chapters 1 to 10 carries a file:line link into the public CueCrux/Crux repository. The audit these chapters were derived from resolved all 347 registrations against commit 93b41a7 with zero unresolved handlers.

0.0 In plain English

An HTTP API is a set of addresses you can send a request to and get a structured answer back. This set documents every one the daemon has. Most of what you will want to know is in the per-topic chapters that follow, one per area: facts, sessions, query, receipts, identity, work, extensions, admin. This chapter is the part that is true of all of them at once.

That is worth having as its own chapter because a good API is boring in a specific way. The same base path prefixes every route. Errors always come back in the same shape, so your client can have one error handler rather than 303 of them. Authentication works the same way on every route that needs it. The same middleware runs before every request in the same order. Once you have read this once, every chapter that follows is just a list of addresses, because the rules never change under you.

You will read this chapter properly once, when you write your first client, and then return to two parts of it. §0.6 when something failed and you need to know how to read the error body, which is RFC 7807 and carries a machine-readable type as well as a human message. And §0.8 when your client starts getting rejected under load, because ingress limits apply before your request ever reaches a handler and the rejection is not coming from the route you called.

The thing people get wrong is assuming an error is an error. The status code alone will mislead you here: a 429 with a Retry-After header is a rate limiter telling you to slow down and try again, and a 503 from the load-shed gate is the daemon protecting itself and is also worth retrying, while a 403 naming a missing scope will fail identically for ever until you change the credential. §0.6 and §0.8 together are what let your client tell those apart, and getting it wrong produces either a retry storm or a client that gives up on a transient condition.

0.1 Base URL and ports

corecruxd is one binary with three listeners, all resolved in load_config (config.rs:793).

PlaneDefault bindHost envPort envCovered by
HTTP (axum)127.0.0.1:14800CORECRUXD_HTTP_HOSTCORECRUXD_HTTP_PORTThis reference (config.rs:801)
MCP (HTTP transport)127.0.0.1:14801CORECRUXD_MCP_HOSTCORECRUXD_MCP_PORTThe MCP tool reference, not this set (config.rs:817)
gRPC (tonic)127.0.0.1:4007CORECRUXD_GRPC_HOSTCORECRUXD_GRPC_PORTChapter 10 (config.rs:812)

The HTTP port default is a fixed contract for every client in the ecosystem. Do not change it.

0.2 Versioning: /v1, and the five paths outside it

There is exactly one API version prefix: /v1. There is no /v2, no /v0, and no unversioned /api. 301 of the 303 paths are under /v1.

Five paths sit outside it:

PathWhy it is outside /v1Chapter
GET /healthzOps probe. Liveness contract predates the API version.8
GET /readyzOps probe. Returns 503 when the node is not ready.8
GET /metricsPrometheus scrape target.8
POST /sessionLegacy invocation rail. openapi.rs:130 calls it a candidate for a future /v1 migration.2
POST /invocation/verifySame legacy rail, same migration note.2

Both legacy rails are deliberately excluded from the daemon's own route manifest (openapi.rs:130) but are still classified by the route-auth layer, so they do not become unreachable under enforce.

Six further paths, the console SPA and its assets, are not API surface at all. They are listed in chapter 9 with their exclusion stated plainly.

0.3 Authentication

CORECRUXD_AUTH_MODE has no default. The daemon refuses to start without it (main.rs:307).

ModeBehaviour
offrequire_http_scopes returns Ok(()) immediately. Every scope check passes, on every route (auth.rs:1320).
dev_scopesScopes come from the X-Corecrux-Scopes header, or from the bearer token parsed as a scope list (auth.rs:385).
jwt_hs256HS256 JWT. Secret in CORECRUXD_JWT_HS256_SECRET; issuer and audience via CORECRUXD_JWT_ISS and CORECRUXD_JWT_AUD (auth.rs:35).
jwt_jwksJWKS or OIDC. CORECRUXD_JWT_JWKS_URL, CORECRUXD_JWT_OIDC_DISCOVERY_URL and friends (auth.rs:44).

Mode strings parse leniently: dev, dev-scopes, jwt, jwks, oidc and several casings all resolve (auth.rs:58).

off disables every scope check in this reference. Under off the auth columns in chapters 1 to 9 describe nothing that is enforced. That is the intended dev-loop posture and it is the wrong posture for anything reachable from a network you do not own.

Headers the daemon reads

HeaderPurpose
Authorization: Bearer <token>Token, or in dev_scopes a literal scope list (auth.rs:373)
X-Corecrux-ScopesScope list, comma- or whitespace-separated (auth.rs:363)
X-Corecrux-Passport-IdActing passport. Trusted verbatim under off and dev_scopes; under JWT modes it must match the token's passport_id claim or the response is 403 PASSPORT_HEADER_MISMATCH (auth.rs:1186). Validated at ingress: 1 to 128 ASCII characters from [A-Za-z0-9._:-] or 400 (ingress.rs:47).
X-Corecrux-Tenant-IdTenant selector (auth.rs:1151)

The complete scope list

There is no canonical scope enum in the codebase. Scopes are string literals at each call site. The full set the daemon uses:

ScopeUsed for
admin:readEvery admin read, and a universal fallback on most read classes
admin:writeEvery admin write, and a universal fallback on most write classes
compute:embedPOST /v1/compute/embed only (compute.rs:29)
events:readgRPC ReadStream only. Not an HTTP scope (grpc.rs:777)
events:writegRPC AppendBatch only (grpc.rs:764)
exports:readReplay exports under /v1/replay/exports/*, incident export
facts:readSubstrate reads, features lens, orchestrators, punchcards, activity, tenant sync
facts:writeEvery fact-store write
integrations:disableDisconnect and disable routes on the integrations planes
integrations:grantConsole integration grant
integrations:installIntegration connect, install, and most non-GET routes on the wide write class
integrations:readIntegration reads
passport:impersonateActing as another passport
passport:readPassport reads
provenance:writeThe BYOK provenance gateway (provenance.rs:622)
query:readRetrieval, projections, studio reads, ops and bootstrap
receipts:readReceipt bodies, signatures, verification, listing
replay:answerAnswer replay under /v1/replay/answers/* (replay.rs:205)
replication:writePOST /v1/internal/replication/segments only
sessions:readSession plan, principal resolve, agent usage
sessions:writeSession state, archive, observations, mediation receipts
tenant:chunks:readConsole chunk listing for a tenant
tenant:content:previewConsole chunk content preview
tenant:metadata:readTenant metadata
tool:invoke:readTool invocation reads

Ten further strings are per-surface Pro capabilities, not general scopes. They gate the Agent Workbench and are listed in §0.12.

The 14-item capability allowlist in an integration manifest is a different, smaller namespace that overlaps these strings. Do not conflate them.

0.4 How to read the auth column

Each route table has an Auth column of the form any-of a, b · Class, where the first part is the check the handler itself performs and Class is the route-auth contract the middleware applies (§0.5).

The daemon has two scope combinators:

  • require_http_scopes(auth, headers, required) requires all of required (auth.rs:1320).
  • require_http_any_scope(auth, headers, any_of) requires any one of any_of (auth.rs:1340).

Across the whole HTTP surface only two handler checks require more than one scope at once:

All-of pairWhere
admin:read and facts:writeEvery mutating extension route (extensions.rs:126), dossier publish (dossier.rs:73), RCX emit (rcx_publish.rs:56), sharing backfill (admin.rs:2559), storybook generate (storybook.rs:49), Studio library install (studio_library.rs:293)
exports:read and receipts:readThe three subject exports (receipts.rs:835)

Everywhere else a multi-scope cell is any-of. The tables say all-of explicitly where it applies.

Tenant binding

Many checks are tenant-bound: require_http_any_scope_for_tenant and require_http_scopes_for_tenant additionally require that the token's tenant claim covers the tenant_id in the request (auth.rs:1359). One carve-out: if the scope that matched starts with admin:, the tenant check is skipped entirely (auth.rs:1380). An admin:read token is therefore cross-tenant by construction. Treat admin:* as the daemon's root credential, not as a convenience.

Request-field notation

MarkerMeans
reqRequired. No serde default; omitting it fails deserialisation.
optOption<T>. Absent is allowed and meaningful.
dfltCarries #[serde(default)]. Absent falls back to the type's default.
dflt <fn>Carries a named default function; the name is given.

0.5 Route authorization

Independent of every handler check, one middleware classifies routes by method and path template and applies a deny-by-default contract (route_auth.rs:75). Its mode is read from CORECRUXD_ROUTE_AUTH once at router build, never per request (route_auth.rs:576).

ValueBehaviourStatus
offPass-through.SHIPPED
shadowThe default. Unset, empty or unrecognised all resolve to shadow. It evaluates the contract, emits marker = "route_auth_shadow_mismatch" on a would-deny, and continues.SHIPPED
enforcePublic routes pass with no auth. Every other route requires any-of its contract scopes. An unclassified route, or a request axum could not match to a template, fails closed with 403.SHIPPED

classify_route() covers all 347 registrations with zero unclassified routes. enforce can be switched on today without any route becoming unreachable. That is a verified property of the current commit, not a design intention.

Route classes

ClassAccepted scopes (any-of)
Publicnone: /healthz, /readyz, /metrics, /session, /invocation/verify, /v1/openapi.json, /v1/version, /v1/witness/smoke, /v1/sync/handshake/nonce, and all of /v1/auth/* (route_auth.rs:77)
InternalReplicationreplication:write (route_auth.rs:98)
AdminReadadmin:read (console reads also accept tenant:chunks:read, tenant:content:preview)
AdminWriteadmin:write (console writes also accept facts:write, integrations:install, integrations:grant, integrations:disable)
Readvaries by prefix, see the map below
Writevaries by prefix, see the map below
FeatureGatedvaries by prefix; carries a documented feature_gate label that the middleware itself never reads. Flag gating stays in the handler (route_auth.rs:44).

Prefix to scope map

PrefixGET acceptsNon-GET accepts
/v1/query/*, /v1/projections/entity/*query:read, admin:readsame; these reads are POSTs
/v1/studio/*query:read, admin:readPOST /v1/studio/library/* needs facts:write, admin:write
/v1/receipts/*, /v1/replay/*, /v1/events/*, /v1/observations/*, /v1/ops/*, /v1/bootstrap/*, /v1/audit/*query:read, receipts:read, exports:read, admin:read-
/v1/cases/retrievequery:read, admin:read-
/v1/cases (record)-facts:write, admin:write
/v1/facts*, /v1/sessions/*, /v1/entities*, /v1/edges*, /v1/kinds*query:read, admin:readfacts:write, sessions:write, admin:write
/v1/features/capabilities*facts:read, admin:readfacts:write, admin:write
/v1/sync/tenants/*facts:readfacts:write
/v1/identity/candidates*admin:read, admin:writeadmin:write
/v1/memory/import, /v1/result-envelope/import, /v1/identity/links, /v1/appendfacts:write, admin:write, admin:readfacts:write, admin:write
/v1/console/*admin:read, tenant:chunks:read, tenant:content:previewadmin:write, facts:write, integrations:install, integrations:grant, integrations:disable
/v1/integrations/*admin:readintegrations:install, integrations:disable
/v1/work*, /v1/status-feed, /v1/projects*, /v1/rcx/publish/*, /v1/workspace/*, /v1/mcp/tools*, /v1/engrams*, /v1/extensions*, /v1/passports*, /v1/principal/*, /v1/policy/*, /v1/relations*, /v1/agents/*, /v1/cost/*, /v1/cloud/*, /v1/actions/*, /v1/workbench/*, /v1/mediation/*, /v1/memory/*admin:read, facts:read, query:read, sessions:readadmin:write, facts:write, integrations:install
/v1/gpu1/*query:read, admin:readsame
/v1/compute/embed-compute:embed
/v1/context*query:read, admin:readsame
/v1/provenance/*-provenance:write, admin:write
/v1/openai/*query:read, admin:read, admin:writesame
/v1/quota*query:read, admin:read-
/v1/credits/*-admin:write
/v1/incidents*query:read, exports:read, admin:readfacts:write, admin:write
/v1/legal-holds*-admin:write
/v1/coord/*admin:read, sessions:readadmin:write, sessions:write
/v1/observe/sessions/*query:read, admin:readfacts:write, admin:write
/v1/orchestrators*, /v1/punchcards*facts:read, admin:readfacts:write, admin:write
/v1/activity*facts:read, admin:readfacts:write, admin:write

Two routes with no handler scope check

The audit found two handlers that perform no scope check of their own and rely entirely on the route-auth middleware. Because that middleware defaults to shadow, on a default install both are reachable without any credential.

RouteWhat the handler takesWhat it exposes
POST /v1/audit/bundle/verifypost_audit_bundle_verify(body: Bytes): no State, no HeaderMap, no scope check (audit_verify.rs:43)An unauthenticated 8 MiB upload that is then decompressed. A second decompressed-size cap inside the verifier returns 413 bundle_too_large, but the upload and the decompression both happen first.
GET /v1/console/onboardingState<AppState> only, no scope check (console.rs:58)The daemon's running auth mode, chosen auth mode, whether the bind is loopback, and allow_insecure_dev_auth_bind. That is the auth posture of the node, unauthenticated.

Both routes are correctly contracted, Read and AdminRead respectively. The gap exists only because CORECRUXD_ROUTE_AUTH defaults to shadow rather than enforce. Setting CORECRUXD_ROUTE_AUTH=enforce closes both. If your daemon is reachable from anything you do not control, set it.

One documentation defect inside the code

route_auth.rs:528 labels the orchestrators and punchcards feature gate as CORECRUXD_AGENTGRAPH (route_auth.rs:528). That environment variable does not exist anywhere else in the codebase. The handlers read CORECRUXD_ORCHESTRATORS (agentgraph_kinds.rs:144) and CORECRUXD_PUNCHCARD (agentgraph_kinds.rs:161). The label is inert, the middleware never reads it, but setting CORECRUXD_AGENTGRAPH does nothing. Chapter 6 documents the real flags.

Sync mutual-auth deferral

When CORECRUXD_SYNC_MUTUAL_AUTH=1 (default off, mod.rs:212), route-auth skips its scope check for exactly five templates, because they are authorized cryptographically by an Ed25519 peer handshake inside the handlers:

/v1/sync/tenants/{tenantId}/manifest
/v1/sync/tenants/{tenantId}/collections/{collection}
/v1/sync/tenants/{tenantId}/promotions/preview
/v1/sync/tenants/{tenantId}/promotions/confirm
/v1/sync/tenants/{tenantId}/offboard

The handshake nonce TTL is 120 seconds (mod.rs:154). CORECRUXD_SYNC_DELEGATION_ENFORCE defaults off, and while off, recipient-bound v1.1 delegation tokens are rejected fail-closed (mod.rs:218).

There is no loopback-only route and no ops token

No HTTP route in this repository is restricted to loopback callers. Loopback matters in exactly two places: onboarding uses http_bind_loopback to decide whether auth_mode = off is permitted (mod.rs:421), and main.rs emits a startup warning when auth_mode = off and both HTTP and gRPC bind to loopback (main.rs:2031). The nearest thing to an ops credential is the admin:read and admin:write pair. There is no separate ops token.

0.6 The error shape

Every failure path goes through problem_response (mod.rs:1788), which serialises ProblemDetails (corecrux-types/src/lib.rs:783) with Content-Type: application/problem+json (problem.rs:27).

{
  "type": "https://errors.cuecrux.com/forbidden",
  "title": "Forbidden",
  "status": 403,
  "detail": "insufficient scopes",
  "code": "MISSING_SCOPE",
  "missingAnyScope": ["facts:write", "admin:write"]
}

type, title and status are always present. detail and instance are omitted when absent. Extension members such as code, missingScopes and missingAnyScope are flattened to the top level, not nested (corecrux-types/src/lib.rs:798).

Read detail. The daemon puts genuinely actionable text there, including which environment variable to flip.

Scope failures

CombinatorcodeExtension member
require_http_scopes (all-of)MISSING_SCOPEmissingScopes, the subset you are missing (auth.rs:1331)
require_http_any_scope (any-of)MISSING_SCOPEmissingAnyScope, the full accepted set (auth.rs:1352)

A request with no scopes at all in dev mode gets 401 with "hint": "set X-Corecrux-Scopes or Authorization: Bearer <scopes>" (auth.rs:857).

Ingress-layer problem types

These are produced before any handler runs, so no route-specific detail is available.

StatustypeTitleExtra headers
400https://errors.cuecrux.com/invalid-passport-headerInvalid X-Corecrux-Passport-Id, (ingress.rs:210)
413https://errors.cuecrux.com/payload-too-largePayload Too Large, (ingress.rs:549)
429https://errors.cuecrux.com/rate-limitedToo Many RequestsRetry-After in seconds (ingress.rs:188)
503https://errors.cuecrux.com/overloadedService OverloadedRetry-After: 1 (ingress.rs:508)
500https://errors.cuecrux.com/internalInternal Server Error, (ingress.rs:521)

A 408 REQUEST_TIMEOUT comes from the router-wide timeout layer (§0.7). A 500 from a handler panic is produced by CatchPanicLayer rather than dropping the connection (mod.rs:1561).

The daemon also publishes a storage-and-transport error-code catalogue, IO_READ_FAILED, SEGMENT_CORRUPT, SHARD_NOT_OWNER, EPOCH_MISMATCH, BACKPRESSURE, TIMEOUT, INTERNAL and others, with their HTTP status, gRPC status and retryability, at docs/error-catalogue.md.

0.7 The middleware stack, in request order

Axum's Router::layer wraps outside-in as calls accumulate: layers added later run earlier. The effective request-path order is the reverse of the source order.

OrderLayerSourceBehaviour
1request_id_middlewaremod.rs:1564Reads X-Request-Id and traceparent; mints a request id when absent; sets x-request-id and traceparent on the response, plus x-trace-id under the otel feature. Emits the http_control structured op-log line with took_ms and status.
2traceparent_middlewaremod.rs:1563No-op unless built with --features otel. With otel, extracts W3C trace context and sets it as the current span parent.
3TimeoutLayermod.rs:156230-second router-wide request timeout, then 408 REQUEST_TIMEOUT.
4CatchPanicLayermod.rs:1561Converts a handler panic into an RFC-7807 500 instead of dropping the connection.
5console static assetsmod.rs:1560Merged after .with_state(state), so console asset routes sit outside every layer below this line.
6route_auth_middlewaremod.rs:1554The deny-by-default contract of §0.5, evaluated over the axum MatchedPath template.
7quota_middlewaremod.rs:1546Per-passport, per-surface token bucket. Pass-through unless CORECRUXD_QUOTA=1 and the path prefix matches CORECRUXD_QUOTA_HOSTED_SURFACES. On deny, 429 plus quota headers, before any metered execution or credit spend.
8presence_middlewaremod.rs:1543If X-Corecrux-Passport-Id is present, spawns a background presence touch. Never blocks; skips the lock entirely when the header is absent. Feeds GET /v1/passports/presence.
9Extension(case_store)mod.rs:1542Injects the shared case store for /v1/cases*.

Route-auth sits outside quota and presence deliberately, so a would-deny short-circuits before any accounting or presence write.

0.8 Ingress limits

apply_ingress_limits wraps both the API router and the MCP router, outside everything in §0.7. Documented order (ingress.rs:19):

passport-header validator → rate limiter → load-shed/concurrency gate
  → inflight gauge → 413 decorator → body limit → routes

A flood is 429'd before it can occupy an in-flight slot, and shedding happens before any body byte is read.

ControlEnv varDefaultEffect when exceeded
Request body sizeCORECRUXD_MAX_REQUEST_BODY_BYTES16 MiB (config.rs:141)413 problem+json. 0 disables.
In-flight concurrencyCORECRUXD_MAX_INFLIGHT1024 (config.rs:147)503 load-shed with Retry-After: 1. 0 disables. Gauge corecrux_http_inflight.
Per-client-IP rateCORECRUXD_RATE_LIMIT_RPS300 req/s (config.rs:150)429 with Retry-After. 0 disables. Counter corecrux_http_rate_limited_total.
Burst capacityCORECRUXD_RATE_LIMIT_BURST600 (config.rs:152)Clamped to at least rate_limit_rps.
Rate-limit exemptionsCORECRUXD_RATE_LIMIT_EXEMPT_CIDRS127.0.0.0/8, ::1/128 (config.rs:155)Loopback is exempt by default, so the console SPA and local agents are never limited.
Trusted proxiesCORECRUXD_TRUSTED_PROXY_CIDRSempty (config.rs:158)Forwarded and X-Forwarded-For are ignored for rate-limit keying until an operator opts in. Behind a reverse proxy with this unset, every request keys to the proxy's IP.
Shutdown drainCORECRUXD_SHUTDOWN_DRAIN_SECS30 s (config.rs:144)Matches the router timeout, so nothing completable is cut short.

Per-route body limits

Four routes get a raised limit at the ingress layer (ingress.rs:54):

RouteLimit
POST /v1/append64 MiB
POST /v1/admin/append64 MiB
POST /v1/memory/import64 MiB
POST /v1/result-envelope/import64 MiB

Three route groups get a lowered or explicit limit inside the router:

RouteLimitSource
POST /v1/audit/bundle/verify8 MiB compressed, plus an independent decompressed-size cap that returns 413 bundle_too_largeaudit_verify.rs:41
POST /v1/compute/embed512 KiBcompute.rs:25
POST /v1/provenance/sign, /verify, /verify-record16 MiB eachprovenance.rs:68

Everything else inherits the global 16 MiB body cap and the 30-second router timeout. No other route sets a per-route timeout.

Rate limiting that is not middleware

  • Community-extension dispatch has a process-wide sliding 60-second window keyed by extension id and passport fingerprint, capped per grant or by a daemon default. It applies to POST /v1/extensions/{id}/tools/{tool_name}/invoke (mod.rs:386).
  • The provenance gateway applies a per-handler rate limit inside its common pre-handler gate, in the order flag, then refuse-spoofable-auth, then scope, then rate limit (provenance.rs:624).

0.9 Correlation headers

HeaderDirectionBehaviour
X-Request-IdRequestHonoured if present; otherwise the daemon mints one.
traceparentRequestW3C trace context. Parsed for correlation always; used to parent a span only under the otel build feature.
x-request-idResponseAlways set.
traceparentResponseAlways set.
x-trace-idResponseSet only when built with --features otel, which is off by default.

Every request also produces one http_control structured op-log line carrying the correlation ids, took_ms and the response status (mod.rs:1619). At 3am, that line and x-request-id are how you tie a client-side failure to a daemon-side record.

0.10 Feature flags and their defaults

A route's existence can depend on a build feature or an environment variable. This table is the full index; each chapter repeats the flag in the affected rows.

Compile-time (Cargo features)

FeatureDefaultEffect
hosted-surfacesoff in Community EditionCompiles in /v1/cloud/access-contract and the seven /v1/gpu1/* routes (mod.rs:1528). Routes and handlers are absent from the default CE binary.
wasm-extensionsoffWithout it, kind: wasm extension dispatch returns 501 (mod.rs:393).
oteloffEnables trace-context propagation and the x-trace-id response header.

Runtime flags that mount or unmount routes

Env varDefaultRoutesBehaviour when off
CORECRUXD_FEATURE_PROVENANCE_APIoff (provenance.rs:47)/v1/provenance/{sign,verify,verify-record}The routes are not mounted at all (mod.rs:1496), so a 404 is returned before any body, including key material, is read. This is the only group in the daemon that unmounts rather than refusing from inside a handler.
CORECRUXD_CONSOLE_ENABLEDon (config.rs:830)6 static console asset routesEmpty router, so 404.

Runtime flags checked inside handlers

Env varDefaultPlaneBehaviour when off
CORECRUXD_COORDon (config.rs:1336)/v1/coord/*404
CORECRUXD_LOCAL_INGESTon (config.rs:1344)/v1/local/ingest404
CORECRUXD_MCP_ENABLEDon (config.rs:827)MCP listener, and the OpenAI shim's tool sourceMCP server not started
CORECRUXD_INTEGRATIONS_ENABLEDon (config.rs:1377)/v1/integrations/*Gated
CORECRUXD_CONTEXT_SURFACEoff (config.rs:1342)/v1/context404
CORECRUXD_AUTO_CAPTUREoff (config.rs:1343)/v1/memory/extract, /v1/memory/candidates*404
CORECRUXD_STREAM_RECEIPTSoff (config.rs:1345)/v1/mediation/receipts stream and context draftsDraft rejected by the legacy parse
CORECRUXD_FEATURE_USAGE_RECEIPTSoff (config.rs:1346)/v1/mediation/receipts usage_ping draftDraft rejected
CORECRUXD_HANDOFF_OBSERVATIONSoff (config.rs:1347)/v1/workbench/handoff-v2No observation written
CORECRUXD_QUOTAoff (config.rs:1362)GET /v1/quota and the quota middlewareRoute 404; middleware pass-through
CORECRUXD_QUOTA_HOSTED_SURFACESempty (config.rs:1363)Quota middleware scopeEmpty means every surface counts as local compute, so unlimited
CORECRUXD_CREDIT_METERoff (config.rs:1373)POST /v1/credits/spend, and the gpu1 rerank burn path404; metered paths keep the legacy no-burn shape
CRUX_MEMORY_IMPORToff (config.rs:1374)POST /v1/memory/import404
CORECRUXD_IDENTITY_LINKSoff (config.rs:1375)/v1/identity/links*, /v1/identity/candidates*, and the candidate extension of /v1/principal/resolve404
CORECRUXD_OPENAI_SHIMoff (config.rs:1376)/v1/openai/tools.json, /v1/openai/invoke404
CORECRUXD_COMPUTE_PROVIDERoff (config.rs:1265)POST /v1/compute/embedRoute stays mounted and returns an explicit capability-disabled envelope
CORECRUXD_ASSEMBLY_CACHEoff (config.rs:1361)/v1/context bundle memoisationCold assembly on every call
CORECRUXD_FEATURE_PASSPORT_MINT_REQUESTSoff (mod.rs:230)/v1/passport/mint-requests/*404 without touching state
CORECRUXD_CONSOLIDATION_SCHEDULERoff (mod.rs:239)Review scheduler, reported by /v1/versionScheduler not run
CORECRUXD_FEATURE_INCIDENTSoff (incidents.rs:34)/v1/incidents*Gated
CORECRUXD_FEATURE_LEGAL_HOLDoff (legal_holds.rs:22)/v1/legal-holds*Gated
CORECRUXD_FEATURE_ACTIVITY_LOGoff (activity.rs:58)/v1/activity*Gated
CORECRUXD_FEATURE_ACTIVITY_LOG_TTL_SECSunsetActivity retention windowNo TTL applied
CORECRUXD_OBSERVEoff (agentgraph_kinds.rs:139)/v1/observe/sessions/*An explicit observe-disabled response
CORECRUXD_OBSERVE_REDACTdefault Audit modeRedaction on observe capture-
CORECRUXD_ORCHESTRATORSoff (agentgraph_kinds.rs:144)/v1/orchestrators*Surface not served
CORECRUXD_PUNCHCARDoff; values off, advisory, enforce (agentgraph_kinds.rs:161)/v1/punchcards*501 (punchcards.rs:598). advisory tracks but never denies; enforce denies on conflict.
CORECRUXD_ENGINE_BASE_URLunset (engine_console.rs:80)/v1/console/engine/*Mediation disabled
CORECRUXD_CORECRUX_BASE_URLunset (console.rs:1229)/v1/console/corecrux/*Proxy disabled
CORECRUXD_GPU1_BASE_URLunset (gpu1.rs:961)/v1/gpu1/* in a hosted buildendpoint_configured: false; compute returns a fallback envelope
CORECRUXD_SYNC_MUTUAL_AUTHoff (mod.rs:212)/v1/sync/tenants/*Scope auth instead of the Ed25519 handshake
CORECRUXD_SYNC_DELEGATION_ENFORCEoff (mod.rs:218)Sync boundaryContextual v1.1 tokens rejected fail-closed
CORECRUXD_RETENTION_DAYSunset, so retention off (mod.rs:374)The compact-facts admin actionOnly already soft-deleted facts are scrubbed
CORECRUXD_ADMIN_FORCE_SEALoffThe force-seal admin action kindRefused
CORECRUXD_OPERATOR_ACTION_MAX_PENDING, _TIMEOUT_SECSsee config.rs/v1/admin/actions queue-
CORECRUXD_ENABLED_PRO_SERVICESempty/v1/workbench/*402 pro_service_not_enabled per surface (workbench.rs:800)
CORECRUXD_USAGE_RECEIPTS_SUBMIT, _ENDPOINT, _CONSENT_ATall absent (config.rs:1348)Outbound usage pingThe submitter never runs. A fresh install dials nothing.

0.11 The Community Edition exclusion

/v1/cloud/access-contract and the seven /v1/gpu1/* routes are compiled in only when the binary is built with the hosted-surfaces Cargo feature (mod.rs:1528). That feature is off in Community Edition.

On a CE binary those eight routes do not exist. Not gated, not 501, absent. They return 404 because nothing is mounted, and the handler code is not compiled. Chapter 3 documents them and marks them FLAG, because they are real routes that some builds serve, but do not plan against them on a CE install.

Even in a hosted build, /v1/gpu1/* needs CORECRUXD_GPU1_BASE_URL set. With it unset, the contract route reports endpoint_configured: false and the compute routes return a fallback envelope rather than an error.

0.12 The Agent Workbench Pro gate

The twelve /v1/workbench/* routes carry a dual gate (workbench.rs:778):

  1. A caller passes with admin:read or admin:write or with the tenant-scoped per-surface capability for that route.
  2. Even then, the capability must appear in CORECRUXD_ENABLED_PRO_SERVICES, or the route returns 402 pro_service_not_enabled (workbench.rs:800).

CORECRUXD_ENABLED_PRO_SERVICES is empty by default, so on a stock daemon every workbench route except GET /v1/workbench/contract returns 402.

RoutePer-surface capability
GET /v1/workbench/briefagent_brief:pro
POST /v1/workbench/context-packcontext_pack:budgeted
POST /v1/workbench/impact-preflightimpact:preflight
GET and POST /v1/workbench/command-ledgerledger:history
GET /v1/workbench/audit-triageaudit:triage
GET /v1/workbench/reasoning-timelinereasoning:timeline
POST /v1/workbench/handoff-v2handoff:v2
POST /v1/workbench/route-proberoute_probe:lab
GET /v1/workbench/api-driftapi_drift:check
POST /v1/workbench/policy-simulationpolicy:simulate

Capability strings are from workbench.rs:46. GET /v1/workbench/contract is not gated: it exists so a client can discover which surfaces are enabled before it gets a 402.

0.13 What /v1/openapi.json does and does not give you

The daemon maintains its own route manifest, const ROUTES at openapi.rs:140, and a drift test asserts it is set-equal to the routes actually mounted (tests/route_spec_drift.rs:757). GET /v1/openapi.json overlays that manifest onto a utoipa-derived base (openapi.rs:499).

The path coverage is exact. The schema coverage is not.

PropertyReality
Paths in the spec but not routednone
Paths routed but not in the spec2: POST /session and POST /invocation/verify, both deliberately excluded (openapi.rs:130)
Method-level mismatches across the 303 shared pathszero
Operations with a full request or response schema26 (openapi.rs:33), facts ×12, query ×4, health ×4, receipts ×3, witness ×1, observations ×1, events ×1
Components (schemas) declared11 (openapi.rs:67)

So /v1/openapi.json describes request and response bodies for 26 of 303 paths, under 9%. The other paths appear as bare entries with a one-line summary, no requestBody and no responses schema. If you were planning to generate a client from it, generate the route list and hand-write the bodies. The drift test cannot catch this gap, because it only compares path and method sets.

That is precisely why these chapters exist and why every table carries request fields and response keys.

0.14 Where each route lives

The router organises the surface into 57 planes. This reference folds them into nine chapters plus gRPC. Counts are method-and-path registrations; the nine chapters sum to 347.

ChapterPlanes coveredRoutes
1. Facts and memoryFacts · Substrate (entities, edges, kinds) · Relations graph · Memory (import, auto-capture, engrams) · Cases · Append and local prose ingest · Result-envelope import · Features lens · Tenant sync49
2. Sessions and handoffsSession handshake and invocation verify · Sessions (state, archive, observations) · Observations and mediation receipts · Activity journal · Agent Workbench · Agent and MCP tool usage · Context surface33
3. Query and retrievalQuery and retrieval · Projections · Events (SSE) · Ops self-observation and bootstrap · Compute provider · Hosted surfaces30
4. Receipts and verificationReceipts and replay exports · Audit bundle verification · Provenance marking gateway · Observe-audit sessions · Incidents · Legal holds25
5. Identity and passportsAuth rails · Identity links and candidates · Passports, mint requests and presence · Principal resolution and capability policy25
6. Work and coordinationWork board, gates and status feed · Coordination plane · Orchestrators · Punchcards · Projects, layers, repos and context graph · Planes · Storybook · Dossiers65
7. Extensions and StudioCommunity extensions · GitHub integration · OpenAI integration · OpenAI function-calling shim · Studio packs and template library · RCX Registry publish · Actions enrichment38
8. Admin and operationsHealth, liveness and version · Routing, shards and GPUs · Admin and operations · Repository registry and code map · Workspace scan and storyline · Cost lens · Quota and credit meter42
9. Console and surfacesConsole API · CoreCrux mediation proxy · Engine mediation · plus the 6 static console asset routes, which are outside the API contract40 plus 6
10. gRPCThe :4007 surface: what is served, and what is compiled but never registered2 services

0.15 Three structural facts worth knowing before you debug

Route ordering is load-bearing. GET /v1/receipts/list must stay registered before GET /v1/receipts/{receiptId} (mod.rs:505). matchit's static-beats-parameter precedence is relied on and covered by a router test. The same pattern protects /v1/extensions/registry against /v1/extensions/{id} (mod.rs:1073) and /v1/studio/library against /v1/studio/pack/* (mod.rs:674).

The same handler can sit in two auth classes. /v1/append and /v1/admin/append share one handler (mod.rs:643) but land in different classes: /v1/append is Write (facts:write, admin:write), /v1/admin/append is AdminWrite (admin:write only).

Adding a route means touching three places. mod.rs mounts it, openapi.rs::ROUTES declares it, and route_auth.rs::classify_route authorizes it. Miss the second and tests/route_spec_drift.rs fails; miss the third and the route fails closed with 403 under enforce.

Sources