Platform · 1. Crux Engine
This chapter is reference. It documents the external contracts of the hosted CueCrux platform service: what you send, what comes back, which failures are worth retrying, and which guarantees stop where.
Crux Engine is closed source. Everything here is a contract or an observable behaviour. Nothing here describes how a result is computed, and that omission is deliberate, see 1.21.
1.0 In plain English
Crux Engine is the hosted half of CueCrux: a service we run, that you send documents to and ask questions of, and that answers with text plus a record of what the answer was built from. If you have used any hosted search or retrieval API, the shape will be familiar. What is different is the second half of every response.
That second half is the reason the service exists. A conventional retrieval stack returns text and asks you to trust it, and when someone later asks where an answer came from, the honest reply is usually that nobody kept the receipt. Crux Engine returns a record alongside the answer: what was retrieved, and what the answer was built from. A third party can check that record without being given access to your data, because citations carry a hash of the quoted span rather than the span itself. That last detail is what makes it possible to hand the evidence to someone you do not want reading your corpus.
The other half of the problem is operational rather than intellectual, and it is the part people underestimate. Running retrieval over somebody else's private documents means keeping tenants apart, metering usage, delegating narrow authority to a backend without handing over the keys, and deleting data properly when asked, including verifying afterwards that it actually went.
You will read this chapter when you are deciding whether to build against the hosted plane rather than run the daemon yourself, and then repeatedly as a lookup while you build. Two sections carry most of that weight: §1.5, the full error contract with retry semantics, which is what your client's failure handling should be written from; and §1.18 and §1.19 read together, where the limits it does not promise are stated as plainly as the guarantees.
The thing people get wrong is treating a response record as proof that the answer is correct. It is not that, and it does not claim to be. It is a record of what the answer stood on, which is a different and more modest thing: it lets a reviewer check whether the evidence supports the conclusion, rather than removing the need for anyone to check. The second misreading is subtler and this chapter is written to prevent it: nothing here describes how a result is computed, so do not infer ranking behaviour from a contract. If a property is not stated as a guarantee, it is not one, however consistently you observe it.
1.1 Which system this chapter documents
Two different things in this platform have been called "the engine". They are not the same component and conflating them will send you to the wrong endpoint.
| Crux Engine (this chapter) | Daemon-side retrieval | |
|---|---|---|
| What it is | The hosted platform service, one service estate behind an HTTP API | A retrieval and reasoning path that runs inside the Crux Daemon binary |
| Where it runs | On CueCrux infrastructure, reached over the public internet through an edge | On your own machine, as part of the daemon you installed |
| How you reach it | /v1 HTTP routes, an API key or an OAuth bearer, a tenant header | Local HTTP, MCP and gRPC listeners on loopback |
| Documented in | This chapter | The Crux Daemon set, starting at Architecture |
Two consequences, stated plainly because earlier copy got both wrong:
- Crux Engine does not ship inside the Crux Daemon. It is the hosted counterpart to the daemon, not a component of it.
token_budgetis a daemon property. The hosted retrieval route has notoken_budget. It haslimit, andlimitis capped at 50. If you have been building against a server-enforced token budget on the hosted API, you have been building against something that does not exist there.
They do share contracts, the receipt formats and the capability-token scheme are the same on both sides, which is the whole point. A workload can move between local and hosted without changing its verification story.
1.2 What it is, and the problem it solves
Crux Engine is the hosted platform service behind CueCrux. One service estate handles ingest, retrieval, answer generation, receipts, identity and tenancy for customers who do not run the local Crux Daemon themselves.
It exists for one problem: evidence-backed answers over private corpora, where the answer is accountable. A conventional retrieval stack returns text and asks you to trust it. Crux Engine returns text plus a receipt, a record of what was retrieved, how it was ranked, and what the answer was built from. A third party can check that receipt without access to your data, because citations carry a hash of the quoted span rather than the span itself.
It also solves the operational half of that problem: multi-tenant isolation, per-tenant corpora, capability-scoped delegation to a hosted backend, credit metering, and erasure that is verified after it commits.
1.3 Position in the architecture
The one-line version: Crux Engine is the hosted plane. CoreCrux is the substrate it reads. The Crux Daemon is the same capability, local.
| Component | Relationship to Crux Engine |
|---|---|
| CoreCrux | The proprietary retrieval and event-storage engine. Crux Engine reads from it and writes receipts back. It stays a separate process by design, see 2. CoreCrux. |
| Crux Daemon | The local-first counterpart. Same receipt formats, same capability-token scheme. The daemon answers locally the question Crux Engine answers hosted. |
| EmbedderCrux | The embedding service. A separate service; Crux Engine does not host embedding models. |
| ReasonerCrux | Local model inference. A separate service. |
| VaultCrux | Now names the trust and verification plane and the public credibility surfaces. It was once a separate hosted product; that product was retired and its code consolidated into this service. Many identifiers still carry the old name. That is history, not architecture. |
| WikiCrux | A consumer of the same receipt and capability-token contracts, through its own backend identity. |
| FeatureCrux | The internal control plane that resolves which runtime behaviours apply to a tenant and a request, and records the decision. There is no public endpoint and no account reaches it, see 3. FeatureCrux. |
Deployment shape. Stateless API and background-worker processes over a shared database. Horizontal scale is by process count; state lives in the database. Schema changes are ledgered per subsystem and forward-only. Public traffic reaches the service through an edge; the service itself is not directly internet-exposed. That is the whole of what is published about the topology, and it is enough to reason about failure modes.
Secrets fail closed at deploy. The deployment has no default values for its API key, signing secret or database password. A deploy that forgets one refuses to start rather than quietly running on development credentials.
1.4 The response envelope
Base path is /v1. OpenAPI 3.0.3 is generated and served at /docs/reference and /docs/reference/openapi.json where enabled.
Every /v1 route returns the same envelope. Success:
{ "ok": true, "data": { }, "meta": { } }
Failure:
{ "ok": false, "error": { "type": "...", "message": "..." }, "meta": { } }
Transport-level failures additionally follow RFC 7807 Problem Details, with these fields:
| Field | Meaning |
|---|---|
type | The error type. One of the 22 values in 1.5. |
title | Short human-readable summary. |
status | The HTTP status code, repeated in the body. |
detail | Human-readable explanation of this occurrence. Read it, where a limit was breached, the limit is stated here. |
instance | Identifier for this occurrence. |
request_id | Correlation id. Quote it in a support request. |
code | Machine-readable code. |
retryable | Whether the condition is recoverable. Mirrors the split in 1.5. |
Individual error types carry additional fields. Those are listed per type below.
1.5 The error contract
There are 22 error type values. Only two are recoverable. Everything else describes a condition on your side, and retrying it without changing the request will produce the same result.
type | HTTP | Recoverable | Extra fields | What it means |
|---|---|---|---|---|
RateLimited | 429 | Yes | retryAfterSec | You exceeded the limit on your credential. Honour Retry-After. |
UpstreamDown | 5xx | Yes | upstream | A dependency the request needed was unavailable. Retry with backoff. |
ValidationError | 400, 422 | No | issues[] on 422 | The request was malformed. 422 carries per-field issues. |
AuthMissing | 401 | No | - | No usable credential, or a required identity header was absent. |
Forbidden | 403 | No | - | The credential is valid but not for this tenant, resource or operation. |
Conflict | 409 | No | details | The request conflicts with current state. |
NotFound | 404 | No | - | No such resource. |
InsufficientCredits | 402 | No | creditBalance | The call was priced and the balance did not cover it. The balance is in the body. |
SponsorRequired | 402 | No | creditBalance, dailyRemaining, upgradeUrl, agentPrincipalId | A self-registered agent principal has exhausted its allowance and needs a sponsor. |
IdempotencyConflict | 409 | No | idempotencyKey | The same idempotency key was replayed with a different body. |
FeatureDisabled | - | No | feature | The requested behaviour is not enabled on this deployment. |
UpstreamContractDrift | 501 | No | upstream, expected, available[] | A dependency answered in a shape this version does not accept. Not a transient fault. |
PolicyBlocked | 412 | No | policy | A policy refused the request before any work was done. |
RcxCapabilityTokenMissing | 403 | No | crux | A capability token was required for this route and none was presented. |
RcxCapabilityTokenRefused | 403 | No | reason_code, token_hash, receipt_hash, issues[], crux | A capability token was presented and refused. reason_code names why, see 1.12. |
DeniedCapability | - | No | decisionId | The acting principal is not permitted this capability. |
DeniedTaint | - | No | decisionId | The request carried data whose provenance is not permitted here. |
ApprovalRequired | - | No | approvalRequestId, decisionId | The action needs a human approval that has not been given. |
SandboxViolation | - | No | violationId, decisionId | The action left the boundary it was permitted to act inside. |
TrustRevoked | 451 | No | digest, decisionId | Trust in the named artefact has been revoked. |
DigestUntrusted | - | No | digest, decisionId | The artefact's digest is not on the trusted set. |
KillSwitchActive | - | No | targetType, targetRef, decisionId | An operator kill switch is active for this target. |
Where the HTTP column is -, the type is raised in the response body by a route rather than mapped from a bare status code. Every one of them is non-recoverable.
The enum carries a small further family of credit-conversion variants. They belong to billing behaviour that is not live, see limit 6 in 1.19, and you will not encounter them.
Status-code mapping, in full. A client that only sees the status code can reconstruct the type:
| Status | Maps to |
|---|---|
| 400 | ValidationError |
| 401 | AuthMissing |
| 402 | SponsorRequired when the body's type says so, otherwise InsufficientCredits |
| 403 | Forbidden |
| 404 | NotFound |
| 409 | IdempotencyConflict when the body carries idempotencyKey, otherwise Conflict |
| 412 | PolicyBlocked |
| 413 | Payload too large. The permitted size is stated in the message. |
| 422 | ValidationError with issues[] |
| 429 | RateLimited, with retryAfterSec taken from the Retry-After header |
| 451 | TrustRevoked |
| 501 | UpstreamContractDrift |
| 5xx | UpstreamDown |
| anything else | ValidationError |
The rule that matters at 3am: retry RateLimited and UpstreamDown. Do not retry anything else. A 401 will still be a 401 in thirty seconds; a 503 may not be.
1.6 Authentication
Five mechanisms. Pick one per request.
| Mechanism | Credential | Notes |
|---|---|---|
| API key | x-api-key and x-tenant-id, both required | The primary mechanism. Rate limit is carried on the key. |
| OAuth bearer | Authorization: Bearer <token> | Session-backed. Issuer and audience are validated. Optional team-seat role resolution. |
| Self-signup session | Authorization: Bearer <token> | The agent self-registration path. Credit-capped and sponsorable. |
| Frontdoor session cookie | Cookie | Browser surfaces only. Cross-origin mutations are blocked. |
| RCX capability token | x-rcx-capability-token | Delegated and capability-scoped. Substitutes for tenant auth on retrieval. See 1.12. |
Four documented behaviours you should build against, not discover:
x-tenant-id is mandatory with an API key. Omitting it is 401 AuthMissing with the message "x-tenant-id is required". It is not inferred from the key.
Header and body tenant disagreement is fatal. If x-tenant-id says one tenant and the request payload or query says another, the request is refused with 403 Forbidden and the message "Tenant mismatch between x-tenant-id header and request payload/query". It is not silently resolved in either direction. There is no precedence rule to learn, because there is no precedence.
A key presented for the wrong tenant is refused, not downgraded: 403 Forbidden, "API key is not valid for tenant".
Auth-backend unavailability is distinguishable from auth failure. If the credential store cannot be reached, the response is 503 UpstreamDown with the failing dependency named in upstream, not a 401. This distinction is the point: a 503 is safe to retry and a 401 is not, and a system that returns 401 when its database is down will send every client into a credential-rotation panic during an outage. Build your client to treat them differently, because the API does.
Deprecated API keys keep working and announce themselves. A response to a deprecated key carries x-api-key-deprecated: true. Watch for that header in your client logs; it is the migration signal, and it arrives before anything breaks.
1.7 The public, unauthenticated surface
These paths require no credential:
/healthz · /livez · /readyz · /metrics · /docs/* · /openapi.json · /.well-known/oauth-authorization-server · /.well-known/crown-keys · /v1/receipts/verify · /v1/receipts/anchor/* · /v1/proof/* · /v1/benchmarks/* · /v1/passports/* · /v1/usage-receipts* · /v1/auth/* · /v1/agents/register · /v1/org/accept-invite · /v1/memory/capabilities
The verification surface is public on purpose. Receipt verification and signing-key discovery do not require an account, because a proof that only the issuer can check is not a proof. You do not need a relationship with us to check our work, and neither does your auditor.
One caveat on that list. /metrics needs no credential but is additionally IP-allowlisted, and /build-info is IP-allowlisted and not on the public list at all. Both exist; neither is an invitation.
1.8 Tenancy and isolation
A tenant is identified by x-tenant-id and cross-checked against the credential.
Corpora are tenant-scoped. GET /v1/corpora returns only the calling tenant's corpora. Corpus visibility is either private or commons.
Retrieval re-checks tenancy at the route, after authentication has already resolved a tenant. A tenantId in the request body that disagrees with the authenticated tenant is 403 Forbidden, "Tenant mismatch for retrieval request".
The guarantee: tenant scoping is enforced at both the credential layer and the query layer, and a mismatch is refused rather than reconciled. The limit of that guarantee: it is a property of this service's request path. It says nothing about what an operator with database access can see, and it is not a claim about encryption at rest.
1.9 Retrieval: POST /v1/retrieve
Hybrid retrieval over the tenant's corpora, and optionally the commons.
Request fields:
| Field | Type | Default | Notes |
|---|---|---|---|
query | string, minimum 2 characters | - | Required. |
tenantId | string | from the credential | Must match the authenticated tenant, or 403. |
agentId | string | default-agent | Attribution for the call. |
corpusIds | string array | [] | Empty means the tenant's default scope. |
limit | integer 1–50 | 8 | Hard maximum 50. Values above it are rejected, not clamped silently. |
lane | light, verified or audit | light | The assurance mode. Selects how much verification work the request pays for. |
includeCommons | boolean | true | Affects cost, see cost_reason below. |
backend | legacy or corecrux-v5 | server default | Per-request override of the retrieval backend. |
Response body: a results[] array. Each result carries chunkId, docId, tenantId, corpusId, content, title, url, score, source (tenant or commons), and scoreComponents with the named members vector, lexical, recency and laneWeight.
Top level, alongside results[]: credit_cost, cost_reason (one of tenant_only_query, paid_tier_commons_query, free_tier_commons_rate), and meta with lane, vectorBackend and tookMs.
scoreComponents tells you which signals contributed to a result's position. It does not tell you how they combine, and that is not an oversight. Treat tookMs as an observation of one request, never as a target.
Declared status codes: 200, 401, 402, 403, 412, 422, 429, 451, 503.
The pointer-first default, and the opt-out
This is the single most likely thing to surprise you. The default response is a pointer-first envelope (crc_v1) in which result content is suppressed. The default payload is cheap on purpose: you get identity, ranking and cost metadata, and you fetch text only for the results you actually want.
To get the full legacy payload with content inline, send the request header:
Accept-Contract: legacy
Old response shapes remain reachable. If you wrote a client against a response with content populated and it suddenly looks empty, you are on the pointer-first default and one header will put it back.
What is safe to say about how retrieval works
Retrieval runs multiple lanes in parallel, lexical and keyword matching, dense vector similarity, with graph and entity signals, and fuses the ranked lists into a single result set. The assurance lane selects how much verification work the request pays for. Each result carries its per-signal score components so a caller can see why something ranked.
That is the whole of the published description. Fusion weighting, per-corpus tuning, query rewriting and the composition of the score are not published, and no future version of this page will publish them.
1.10 Answers: POST /answers
The answer product surface. Evidence for a produced answer is retrievable at GET /answers/:id/evidence.
| Field | Type | Default | Notes |
|---|---|---|---|
q | string, 3–2000 characters | - | Required. The question. |
question_date | YYYY-MM-DD | - | Anchors relative-date resolution to your clock rather than the service's. |
topK | integer 1–100 | 10 | Hard maximum 100. |
rerankK | integer 1–100 | - | Hard maximum 100. |
hydrate | pointer, summary or full | full | How much evidence text comes back inline. |
mode | light, verified or audit | verified | The proof mode. Note the default. |
allowLightFallback | boolean | false | See below. This default is the guarantee. |
freshness | object | - | since, sinceDays, bias, max_snapshot_age. |
audience | tenant or external_share | tenant | Governs what may be included for an external reader. |
dataRegion | enum | - | See below. |
corpusIds | string array, maximum 200 | - | Scope restriction. |
filters | object | - | domain[], since, licenseAllow[], excludeRisk[]. |
mode defaults to verified, and allowLightFallback defaults to false. Together these mean: if you ask for a verified or audit answer and the proof dependencies for that mode are unavailable, the request fails with 503. It does not quietly return a light answer wearing a verified label. Weakening the proof contract requires you to opt in explicitly by sending allowLightFallback: true.
That is the behaviour an auditor should ask about, so it is stated here rather than buried: a failed verified answer is a 503 you can see, not a weaker answer you cannot.
dataRegion is a first-class request field. The enum the contract accepts is eu-west, eu-central, uk, us-east, us-west, ap-southeast.
Read that precisely. The enum is what the API accepts, not a statement of what is provisioned. Confirm the regions available to your account with us in writing before you design a residency posture around a value in this list. Publishing the accepted enum without that caveat would be the kind of silence that reads as a promise.
1.11 Ingest: POST /v1/ingest
Ingest is asynchronous. A successful submission returns 202 Accepted; the work happens on a background worker.
Declared status codes: 202, 401, 403, 409, 412, 413, 422, 429, 451, 503.
Body size is capped by configuration, and the cap is discoverable from the error. A breach returns 413 with the applicable limit stated in the message. You do not need to guess it or find it in a table that may have drifted, provoke it once and read the response.
Ingest modes are light, verified and audit, matching the retrieval and answer lanes. The semantic difference is trust metadata: light stages documents with incomplete trust metadata; verified and audit require the full metadata set and reject a document that cannot supply it.
1.12 Capability tokens (rcx-ct)
An rcx-ct is the delegation scheme by which a holder, typically a Crux Daemon acting for a tenant, is authorised to call a hosted backend on that tenant's behalf. It is presented as x-rcx-capability-token and, on retrieval, substitutes for tenant authentication.
Spec versions: rcx-ct/1.0 is the base; rcx-ct/1.1 adds delegation. Both are accepted. Signatures are Ed25519, 128 hex characters; hashes in the token are 64 hex characters.
Token structure:
| Field | Contents |
|---|---|
spec_version | rcx-ct/1.0 or rcx-ct/1.1 |
token_id | Unique identifier for this token |
issued_at, expires_at, refresh_hint_at | Validity window, plus the point at which the holder should refresh |
issuer | passport_kid, issuer_org |
subject | passport_fpr, daemon_instance_id |
tenant_scope | The tenant this token acts for |
team_scope, enterprise_scope | Optional broader scopes |
tier | The entitlement tier the token carries |
receipt_class | The class of receipt calls under this token produce |
backends[] | The permitted backends. Each carries backend_id, trust_root_kid, endpoint_url, permitted_capabilities[] and credit_cost. |
credits | The credit allowance bound to the token |
Each entry in permitted_capabilities[] carries a capability, a set of data_egress_classes[], and any required_attestations[].
Data-egress classes are the safety property worth understanding. A token names exactly which classes of data may leave under it:
| Class | Meaning |
|---|---|
none | Nothing leaves |
vectors | Embeddings only |
receipt_hashes | Receipt hashes only |
constraint_records | Constraint records |
decision_records | Decision records |
encrypted_blob | Opaque encrypted payloads |
text | Plain text |
Anything not named is refused with egress_not_permitted. A token that permits only receipt_hashes cannot be used to move your prose off the machine, and that is enforced at validation rather than promised in a policy document.
Refusal codes are a published enum. A refused call tells you which one applies:
token_invalid · token_expired · token_signature_invalid · token_revoked · tenant_mismatch · principal_not_scoped · issuer_not_trusted · backend_not_permitted · trust_root_mismatch · capability_not_permitted · egress_not_permitted · insufficient_credits
Revocation reasons are published too, so a revoked token says why it was revoked:
revoked:principal_terminated · revoked:superseded · revoked:suspected_compromise · revoked:policy_change · revoked:credit_default
Degradation is observable. Every RCX-mediated response carries an X-Crux-Mode header with one of:
X-Crux-Mode | Meaning |
|---|---|
local | Served locally |
hosted | Served by the hosted plane |
customer_hosted | Served by a backend the customer runs |
degraded-local | The hosted path was unavailable; a local path served it |
degraded-queued | The call was queued rather than served |
refused | The call was refused |
You can always tell which mode served you. A degraded answer does not arrive disguised as a normal one. The queue has a default time-to-live of 300 seconds; that is a default, overridable per token, and not a delivery guarantee.
Error behaviour on retrieval: a missing token where one is required is 403 RcxCapabilityTokenMissing; a malformed token is 422; a refused token is 403 RcxCapabilityTokenRefused carrying reason_code, token_hash and receipt_hash.
Endpoints:
| Endpoint | Purpose |
|---|---|
POST /v1/rcx-ct/validate | Validate a token. The verification side of the scheme. |
POST /v1/rcx-ct/refresh | Exchange a token approaching expiry for a fresh one. |
POST /v1/rcx-ct/revoke | Revoke a token. |
POST /v1/rcx-ct/issue | Mint a token. Issuer-only. |
POST /v1/rcx-ct/issue-delegation | Mint a narrowed delegated token. Issuer-only. |
POST /v1/rcx-ct/refund | Return credits reserved against a token. |
The boundary: anyone can verify a token; only the issuer can mint one. Verification takes a public key, the canonical token fields and Ed25519; everything a third party needs. Issuer key custody, how a delegated token is narrowed, and how credits are debited and refunded are not published and will not be.
1.13 Receipts and the verification plane
Two receipt kinds, two different guarantees. Collapsing them into one stronger claim is the most common error made about this system, including in our own earlier copy. Do not do it.
Execution receipts are Ed25519-signed and chained. POST /v1/receipts/verify returns a signature object carrying crown_signature, signing_kid, signing_pub, verified and algorithm: "ed25519". The chain[] walks parent_receipt_hash links and reports signature_verified per entry. When an execution receipt is unsigned, valid comes back false with reason: "receipt_unsigned". It does not fudge.
Retrieval receipts are not signed. The API states this itself, and this is its wording:
Retrieval receipts are content-addressed via blake3(canonical inputs). They are not Ed25519-signed; verification is membership in the public benchmark receipts table.
Read what that does and does not give you. Content addressing gives you tamper-evidence: the same canonical inputs produce the same hash, so a changed input produces a different hash. It does not give you an issuer attestation, because nothing signed it. If your control requires a signature, you need an execution receipt.
POST /v1/receipts/verify, unauthenticated, deliberately.
Request: receipt_hash (32–128 hex characters), optional chain_depth (1–100, default 20).
Response: valid, kind, chain_proof_format (currently "v1"), receipt, signature (or null), chain[], reason.
Response headers: Cache-Control: public, max-age=60 and X-Robots-Tag: noindex, nofollow. The cache header is a caching hint, not a freshness guarantee.
An unknown hash returns 404 NotFound, "Receipt not found in any public benchmark set".
GET /.well-known/crown-keys, public signing-key discovery, cached 300 seconds. Returns { keys: [{ kid, algorithm: "ed25519", publicKeyB64, keyVersion }] }. This is what a third-party verifier fetches to check a signature without asking us anything.
GET /v1/receipts/anchor/:receiptId, public anchor lookup for a receipt.
POST /v1/invocation/verify, verification of an invocation record.
The CROWN receipt payload, at field level:
| Group | Fields |
|---|---|
timings | retrieveMs, rerankMs, llmMs, totalMs |
retrieval | topK, rerankK, filters, audience, allowed_corpora |
selection | citationIds[], coverage, distinctDomains, fragilityScore, loadBearingCitations |
counterfactual | found, note, citations[] |
| citations | { id, quoteHash } |
Citations carry quoteHash, not the quote. The receipt proves provenance without carrying your content. That is what makes it safe to hand a receipt to a third party who is not entitled to see the corpus.
What a receipt does not prove. A receipt is a verifiable record of what was stored and retrieved. It is tamper-evidence, not attestation of conduct; it does not prove that an agent did a thing in the world, only that this evidence set produced this output under this configuration. And there is no customer-facing replay endpoint. If you have read a claim that an auditor can replay any past query against the historical corpus state and confirm the same inputs produce the same outputs, that claim was wrong; there is no such endpoint on this API today. Verification is what is offered, and verification is what 1.7 makes public.
1.14 Health and readiness
| Path | Access | Response |
|---|---|---|
GET /livez | public | { "status": "ok" } |
GET /healthz | public | { "status": "ok" } |
GET /readyz | public | 200 with status and checks, or 503 with status: "error", reason and checks |
GET /health | public | service, status (healthy or degraded), checks |
GET /build-info | IP-allowlisted | Build metadata |
GET /metrics | IP-allowlisted | Prometheus text |
Use /livez, /healthz and /readyz as your integration contract. /readyz returns 503 when the service is not ready to serve; it is a gate, not a diagnostic, and what it checks internally is not published.
1.15 Rate limiting
Rate limits are per credential, not per account and not per route. An API key carries its own limit, set when the key is issued; session-backed credentials carry a standard session limit.
Do not hard-code a number. Read it from the response. The limiter emits IETF draft-form headers on every response:
RateLimit-Policy: <limit>;w=<window>
RateLimit: <remaining>;w=<window>
On breach you get 429 with retry-after in seconds and error type RateLimited, which is one of the two recoverable types. Back off by the stated interval.
Limits are a per-key operational parameter and may change at issuance. They are not a throughput commitment.
1.16 Client compatibility: idempotency and versioning
Idempotency. Mutating endpoints accept an idempotency key. Replaying the same key with the same body is safe; replaying the same key with a different body returns 409 IdempotencyConflict with the offending idempotencyKey in the body. That is a client bug being surfaced, not a transient condition, do not retry it.
Versioning policy, as observed, so you can plan against it:
| Mechanism | Policy |
|---|---|
| API version | By path prefix. Everything in this chapter is /v1. |
| Response contract | Content-negotiated. The pointer-first default and the legacy shape are both reachable; Accept-Contract: legacy selects the older one. Old shapes stay reachable rather than being removed. |
| Capability tokens | Versioned in band. rcx-ct/1.0 and rcx-ct/1.1 are both accepted. |
| API keys | A deprecated key keeps working and announces itself with x-api-key-deprecated: true. Deprecation is a signal before it is a break. |
| Receipt chains | Carry chain_proof_format, currently "v1". A verifier should branch on it rather than assume it. |
| New behaviour | Ships disabled by default and is enabled deliberately. The only behaviour on by default is behaviour that is there for safety or correctness. |
The practical consequence: read chain_proof_format, set Accept-Contract explicitly rather than relying on a default, and treat x-api-key-deprecated in your logs as work to schedule rather than noise to filter.
1.17 Lifecycle of a request
Described behaviourally. This is what happens to your request, in order, and where it can stop.
- Admission. Authentication resolves a credential to a tenant. A mismatch refuses with 401 or 403. A failure of the credential backend itself is a distinct 503, never a 401.
- Guarding. A policy layer may refuse before any work is done,
412 PolicyBlocked,451 TrustRevoked, or one of the denial family. Nothing has been retrieved or charged at this point. - Capability check. If the call is capability-mediated, the token is validated against backend, capability and egress class. A refusal carries a named
reason_codeand the response is stamped withX-Crux-Mode. - Retrieval. Multiple lanes run in parallel over the tenant's corpora, plus the commons if requested, and are fused into a single ranked set. The assurance lane governs verification depth.
- Selection and synthesis (answers path only). Candidates are reduced to a citation set and the answer is generated with explicit links to the supporting evidence.
- Metering. The call is priced and charged. An insufficient balance is a
402with the balance included in the body, a visible refusal, not a silent degradation. - Receipting. A receipt is written recording the query, evidence identity, ranking and timing. Execution receipts are signed and chained; retrieval receipts are content-addressed and are not signed.
- Response. Pointer-first envelope by default; full payload on
Accept-Contract: legacy.
1.18 What it guarantees
Eight guarantees. Each one is stated with its boundary in 1.19, and the two sections are meant to be read together.
- Tenant isolation is enforced twice, at the credential layer and again at the query layer. A mismatch is refused, never reconciled.
- Proof modes do not silently degrade. A
verifiedorauditrequest whose proof dependencies are unavailable fails with 503. Weakening it requiresallowLightFallback: truefrom you. - Receipts are verifiable without access to your data. Citations carry
quoteHash, not quotes. - Verification is public and unauthenticated.
/v1/receipts/verifyand/.well-known/crown-keysneed no account. You do not need a relationship with us to check our work. - Data egress is capability-scoped. A capability token enumerates the exact egress classes permitted; anything else is refused with
egress_not_permitted. - Degradation is observable, never silent.
X-Crux-Modeis stamped on every capability-mediated response. - Deletion is verified after commit. Document deletion runs a post-commit erasure verification and reports
erasure_verified. A tenant-wide erasure path exists. A failed verification is raised as an alarm condition, not swallowed. - Secrets fail closed at deploy. The deployment carries no default credentials. A deploy missing a required secret refuses to start.
1.19 What it does not promise
Six limits. They are published here at the same volume as the guarantees, because a guarantee without its boundary is marketing.
1. There is no latency or availability SLA. None exists. Any number you have seen, a tookMs in a response, a benchmark figure, a cache TTL, a healthcheck interval, is an observation or a configuration value, not a commitment. If you need a service level, it has to be a commercial agreement written deliberately, and you should ask for one rather than infer one.
2. Retrieval receipts are not signed. Content-addressed only. The API says so in its own response body and 1.13 quotes it. If your control requires an issuer signature, use an execution receipt.
3. The result caps are hard. limit on /v1/retrieve is capped at 50. topK and rerankK on /answers are capped at 100. There is no request that lifts them and no tier that raises them.
4. Governance-tier entitlements are granted but not enforced. A set of governance capability keys is reserved and inert by design. They are granted on the entitlement record and nothing gates on them. Do not treat them as live controls, do not build an assurance argument on them, and do not accept them being described as active capabilities. When they become enforcing, that will be a documented change.
5. Billing features are default-OFF and not live. The subscription credit-grant and payment-provider integrations are implemented and gated off. No live pricing is configured. Nothing in that surface should be treated as available today.
6. The substrate-backed fact store does not support aggregation by category. Where a deployment routes the fact store to the substrate engine, a request to aggregate facts by category is refused with an explicit unsupported-operation error rather than degrading to a partial or approximate count. A wrong number would be worse than a refusal.
One further boundary, not a limit so much as a scope statement: which fact-store backend a deployment runs is an operator choice, not a property of the product. Do not assume either one from this page.
1.20 Claims corrected from earlier published copy
Earlier public pages carried the following. Each is wrong or over-stated for this service, and none of them should be quoted from an archived copy.
| Claim that was published | Status |
|---|---|
| "Adaptive Manifest Routing decides per query which lanes earn their tokens, learning from outcomes rather than knobs" | Not a feature of this service. Withdrawn. |
"The exit is a hard token_budget enforced server-side" | A daemon property, not this API. This API has limit, capped at 50. |
| "The fusion weights are tunable per corpus class and query intent" | Withdrawn. Fusion configuration is not published. |
| "Candidates are re-ranked on relevance, recency, source authority, and coverage diversity" | Withdrawn. The ranking signal set is not published. |
| "An auditor can replay any past query against the historical corpus state and confirm the same inputs produce the same outputs" | Withdrawn; this is the important one. There is no customer-facing replay endpoint. As written it was an auditable promise that could not be honoured. |
| "The receipt captures the original query, the retrieved chunks with their ranking scores, the synthesis rationale, and a BLAKE3 content-hash chain" | Withdrawn. It collapsed signed execution receipts and unsigned content-addressed retrieval receipts into one stronger guarantee, and "retrieved chunks" overstates a receipt that carries quoteHash rather than content. See 1.13. |
| "Ships inside the Crux Daemon" | Category error. See 1.1. |
| "Two lanes run in parallel: sparse keyword matching (BM25) and dense vector similarity" | Understated for this service, whose response exposes four score components. See 1.9. |
1.21 Grounding, and what is deliberately absent
Crux Engine is a closed system. This chapter carries no source links, because the paths would themselves be the disclosure. Every claim above is grounded on something you can observe from outside: a route, a request field, a response field, a header, a status code or an error type. If a statement here is wrong, a request will prove it wrong, which is the only kind of grounding that is worth anything to a reader who cannot read the source.
What is deliberately absent, and will stay absent: scoring formulas and fusion constants; query rewriting and expansion strategy; the full lane inventory and which lanes are gated; ranking-signal weighting; internal module, table and column names; hosts, addresses, container names and internal ports; feature-flag names; issuer key custody and delegation derivation; and credit debit and refund accounting.
What is not absent, and should never be: request fields, response fields, status codes, headers and error codes. Those are contracts. Withholding a contract helps nobody.
Next: 2. CoreCrux for the substrate this service reads, 3. FeatureCrux for the internal control plane, and 4. How they fit together for the whole picture and what stays yours in each deployment shape.

