# HTTP Contract v1

All successful responses use `{ data, meta }`. All errors use `{ error, meta }`. `meta.request_id` matches the `X-Request-ID` response header and `meta.api_version` is `v1`.

Clients may send an `X-Request-ID` containing up to 100 safe ASCII identifier characters. MemPlumb generates one when absent or invalid.

When OpenTelemetry is enabled, MemPlumb extracts the W3C `traceparent` and `tracestate` request headers, creates a server Span, and returns the active `traceparent` response header plus `meta.trace_id`. Invalid or absent trace context starts a new trace. Baggage is intentionally neither extracted nor propagated because arbitrary baggage can contain tenant or personal data. `trace_id` is optional so tracing can remain disabled with zero exporter overhead.

## Runtime probes

`GET /health` is a liveness probe. It reports the active policy/release without querying storage, so a supervisor does not restart a healthy process merely because its database is temporarily unavailable.

`GET /ready` is a traffic readiness probe. It verifies storage connectivity, migration-ledger integrity, pending schema versions, and the active Context Policy's semantic-index requirement. Success includes a bounded Runtime projection, an optional non-secret launcher instance ID, and a `semantic_index` object with required/ready/degraded state and, when applicable, backend, provider key, dimensions, total/indexed/pending counts, coverage, minimum coverage, profile state/version, and failure mode. It never includes credentials, vectors, Memory IDs, or database details. A fail-closed `hybrid_ann` profile must be provisioned, dimension-compatible, ready, and sufficiently covered. `portable_fallback` may return ready with `degraded: true`. Failure returns `503 NOT_READY` with a stable public message; connection strings and underlying provider/database errors are not exposed.

## Local launcher control

`GET /v1/local-runtime` and `POST /v1/local-runtime` exist only on a launcher-created Runtime using the private local legacy credential. Both require the Bearer credential and exact `X-MemPlumb-Instance-Id`. The identity response binds Runtime/Build metadata, process ID, instance ID, and Workspace; the launcher separately matches the database profile held in its sensitive local state. `POST` acknowledges a graceful shutdown request before the Runtime closes workers, HTTP connections, tracing, and storage. A generic `serve`, managed-auth Runtime, wrong instance, or wrong credential cannot use this control path.

`POST /v1/local-runtime/bootstrap-tokens` uses the same authenticated ownership fence to mint a 256-bit browser bootstrap token. Each token expires after 60 seconds, remains independently usable during concurrent tab opens, and is held in a bounded pending set. `POST /v1/local-runtime/bootstrap` atomically exchanges one token once for the local credential; replay, malformed, and expired tokens fail. This keeps the reusable administrator credential out of browser-launch process arguments while allowing a fresh tab to connect without manual entry.

## Session and operations overview

`GET /v1/session` returns the authenticated Workspace, managed evaluator binding, granted scopes, and final server-computed action capabilities. Capabilities include Memory, Pipeline, Evaluation, Quality, Change Feed, Webhook, and operations actions. They are not a direct scope echo: `admin` permits Quality audit and configuration but does not grant independent `quality_work` or `quality_resolve`; managed evaluation writes also require an evaluator-bound key. `evaluation_execute` permits Memory Case curation and Replay Run create/cancel/resume, but it does not grant full Case export or evaluator identity. Legacy and unauthenticated-local modes are identified without returning a socket address. The response is `Cache-Control: no-store`.

`GET /v1/operations/overview` requires `admin`. It combines a bounded Runtime projection, active Memory/Context Policy and release attribution, storage and semantic readiness, and exact Memory, Pipeline, Quality, Outcome, and Evaluation counts. `counts.outcome.authenticated_successful_context_outcomes` counts only Outcomes with `evaluator_authentication=authenticated`, `task_success=true`, and `safety_pass=true` whose associated Context Run has a non-empty `memory_ids` array. Evaluation counts include Memory Cases, Case sources, Replay Runs by state, completed passing runs, and completed runs containing regressions. Both Store adapters compute the nested counters in one database statement so all counts share one statement snapshot. The response does not contain event rows, Context queries, Memory values, executable paths, credentials, or raw evidence. Recent activity must be read through the separately authorized paged resources.

## Stable Pipeline pages

`GET /v1/events` requires `memory:read` and returns only `id`, `actor_id`, `type`, `status`, `created_at`, and `completed_at`. Event text and metadata remain available only through the existing exact-ID trace route. Filters are `actor_id`, `type`, `status`, inclusive `since`, exclusive `until`, and `limit` from 1 through 200.

`GET /v1/context-runs` also requires `memory:read`. Each summary includes the query, budgets and counts, selected Memory count, Policy/release attribution, provider/retrieval metadata, and timestamps. It deliberately omits assembled Context text, Memory IDs, scores, and ranking arrays; `GET /v1/context/:contextId` remains the full sensitive evidence route. Filters are Actor, release, Context Policy, inclusive `since`, exclusive `until`, and the same page limit.

Both resources order immutable `(created_at, id)` tuples descending and return `{items,next_cursor,has_more}`. `next_cursor` is an opaque, resource-bound, versioned cursor; clients must pass it unchanged as `after` and must not parse or reuse it across resource types. Newer inserts do not shift subsequent pages.

## Event idempotency

`POST /v1/events` requires `Idempotency-Key`.

- First request claims the key before extraction begins.
- Same key and payload while processing returns `409 REQUEST_IN_PROGRESS`.
- A processing claim has a five-minute Store lease. After expiry, the same Actor and canonical request can take over; a different Actor or payload conflicts.
- Same key and payload after completion returns the original event with `meta.idempotent_replay=true`.
- Same key with a different payload returns `409 IDEMPOTENCY_KEY_REUSED`.
- Failed requests release the key into a retryable failed state.

Keys are scoped to the active workspace and persist across daemon restarts. The internal owner token has 256 bits of entropy, is stored only as a SHA-256 hash, and is never returned through HTTP, Actor export, or Workspace Snapshot. Completion and failure require the current token; completion also requires the Event to belong to the Actor bound at claim time. Actor hard purge removes processing, failed, and completed claims, and forced Workspace replacement clears the ledger. A Store Adapter must advertise and implement the mandatory `idempotency_event_fence` capability.

## Context evidence

`POST /v1/context` requires a non-blank `actor_id` and returns the durable `context_id`, `release_id`, `context_policy`, Embedding Provider description, semantic/cache status, bounded candidate count, strategy/backend, limit, channel counts, truncation state, filtered-memory reasons, and an ordered selected-memory list. Portable-postings responses additionally identify the index version, query-term count, and reserved budgets. `hybrid_ann` responses add semantic/lexical/protected/recent budgets and counts, embedding coverage, index state, degraded state, ANN backend/version, semantic count, and any fallback reason. Each selected Memory includes `retrieval_score` and `retrieval_explanation` with raw components and weighted contributions. `candidates` is the number of rows considered, not the Actor's total active-memory count.

Candidate generation applies Workspace, exact Actor, active status, `valid_from`, and `expires_at` predicates before candidate Memory text reaches an Embedding Provider. The default `portable_postings` strategy reports backend `portable-postings-v2`, reserves lexical/protected/recent shares, and spills unused capacity. Its tokenizer keeps Latin/alphanumeric runs whole and adds CJK 2/3-grams for phrase-substring matching. `recent_scan` is a recent-only compatibility backend.

`hybrid_ann` embeds the query once, then asks a provisioned PostgreSQL `pgvector` HNSW profile for a scoped semantic channel before merging it with lexical/protected/recent channels. Candidate signals contain only `semantic_similarity`; raw query and Memory vectors are never in the HTTP envelope or Context evidence. `ann_failure_mode: "fail_closed"` returns `503 SEMANTIC_INDEX_NOT_READY` (or `SEMANTIC_INDEX_UNAVAILABLE` on SQLite). `portable_fallback` continues through portable channels and makes the degraded state explicit. ANN is semantic candidate retrieval, not a Knowledge Claim Graph or relationship inference API.

`GET /v1/context/:contextId` returns `evaluated_at`, selected ranking evidence, feedback, retrieval adjudication revisions, outcomes, and ordered `retrieval_candidates`. Each candidate records `memory_id`, exact `memory_version`, ordinal, eligibility, optional selected rank/score, candidate-source channels, Policy explanation, and exclusion reasons. This is a sensitive audit surface under `memory:read`; clients should not assume score component names beyond the active Context Policy contract.

## Retrieval adjudication and quality datasets

`POST /v1/decisions/:decisionId/write-adjudications` requires an evaluator-bound managed `evaluation:write` key and `Idempotency-Key`. The Runtime resolves the exact content-addressed Write Decision Observation and derives evaluator ID/type/principal from the key; request bodies cannot override workload or evaluator identity. A new append-only revision returns `201`, an identical retry returns the original record with `200`, and key reuse for different content returns `409`.

`GET /v1/write-adjudications` requires `evaluation:read` and returns privacy-bounded summaries with Decision, Actor, rubric, evaluator, authentication, and latest-only filters. `GET /v1/write-adjudications/:adjudicationId` requires `review:audit` and returns the full note and evidence with `Cache-Control: no-store`. Latest/authenticated filtering never falls back to an older authenticated revision when the actual latest evaluator revision is unverified.

`POST /v1/context/:contextId/retrieval-adjudications` requires `evaluation:write` and `Idempotency-Key`. In managed-auth mode the key must be evaluator-bound; the server derives evaluator ID/type and rejects conflicting body assertions. Actor, release, Context Policy, candidate state, ordinal, and rank come from the referenced Context Run rather than the request body. Identical replay returns the original revision with `200`; a new revision returns `201`; reuse by a different principal, Context, or payload returns `409 IDEMPOTENCY_KEY_REUSED`. Raw idempotency keys are stored only as hashes.

If a stable quality review task is active for the same Context and rubric, this
direct route rejects a managed authenticated write. The reviewer must use task
`complete`, which atomically revalidates assignment, signal basis, generation,
lease owner/version/token, and database-clock expiry before appending the
adjudication. Unmanaged writes remain possible only as `unverified` audit
evidence and cannot complete the production task.

Retrieval evaluator identity defaults to `unverified`. A local, legacy, or unmanaged path remains auditable but cannot enter the authenticated-only production dataset. Where the server derives a domain-separated, non-secret pseudonymous `evaluator_principal_id` for idempotency, its presence still does not authenticate the evaluator. Only a managed evaluator-bound API key produces trusted retrieval adjudication evidence.

An `accepted` adjudication must contain an effective `positive`, `hard_negative`, or missed-positive label. Candidate labels must identify the exact `memory_id` and `memory_version` captured by that Context Run. A missed positive supplies a Memory ID outside the observed candidate pool; the Store resolves and pins the exact Actor-owned revision visible at `evaluated_at`. A `rejected` proposal may contain only ignored labels and remains audit evidence without entering the production dataset. Ordinary Feedback and Outcomes can create review signals but are never inferred as labels.

`GET /v1/retrieval-adjudications` requires `evaluation:read` and supports Context, Actor, rubric, evaluator, segment, time-window, authentication, latest-revision, and bounded-limit filters. `GET /v1/quality-review-queue` also requires `evaluation:read`; `rubric_version` is mandatory, `status` is `pending`, `adjudicated`, or `all`, and repeated `signal` parameters select review signals. The queue returns bounded metadata and summaries, not queries, Memory values, notes, or evaluator evidence. If an effective evidence control quarantines the latest accepted adjudication, the item reopens under `pending` and its summary reports `latest_adjudication.quarantined=true`. Exact-window restore returns it to `adjudicated` only when that same accepted adjudication is not older than any current review signal; the API never falls back to an older revision.

The v0.6 production coordination surface keeps stable tasks under
`/v1/quality-review-tasks`. `GET` lists stable `(Context, rubric)` tasks and can
filter by `context_id`, `actor_id`, `rubric_version`, `state`, `evaluator_id`,
and bounded `limit`. `GET /v1/quality-review-tasks/:taskId` returns one task and
its bounded event history. Summaries expose generation, revision, projected and
stored state, signal types/count, logical assignment, attempt count, non-secret
lease version/active expiry/owner, and adjudication identity. Exact signal
references and the internal basis fingerprint are not public, and neither is a
raw or hashed lease token.

For panel review, `assign` configures reviewer count N, strict-majority quorum
Q, server-enforced blind mode, wait-for-all behavior, deadline, and up to nine
logical evaluator assignments. `GET /v1/review-work` returns only the caller's
slot plus the exact Context and Memory revisions needed to judge it. Slot
`claim`, `renew`, `release`, and `submit` use independent client-generated lease
capabilities and monotonic fencing versions. An exact-decision-hash cohort of Q
decides the round; abstentions are terminal but do not vote; no quorum becomes
disputed.

Disputed rounds use a separate fenced resolution lease. It requires an
evaluator-bound explicit `review:resolve` scope; admin alone cannot resolve, and
a round participant cannot become its resolver. The resolver sees anonymous
Reviewer A/B/C decisions and either selects an immutable submission or writes a
reasoned override. Full participant identity and append-only timelines require
`review:audit` or admin. See [quality-review.md](./quality-review.md).

The v0.5 N1/Q1 task wrapper remains compatible.
`POST /v1/quality-review-tasks/assign` changes logical reviewer assignment for
the supplied Context and rubric. `POST /v1/quality-review-tasks/claim` acquires
an explicit `task_id`, a Context/rubric task, or the next eligible task for the
rubric and signal filters. Claim sends its client-generated `rlt_` plus 64-hex
256-bit token in `Review-Lease-Token` and an `Idempotency-Key`; it returns
`Cache-Control: no-store` and the raw token once. `renew`, `release`, and
`complete` remain task-ID actions. They require the token header and current
`owner_id`/`lease_version`; complete also requires `Idempotency-Key` and
atomically records an authenticated accepted/rejected adjudication plus the
resulting `completed`/`needs_escalation` task revision. Lease durations are
5-3600 seconds. Assignment eligibility is derived from the key-bound evaluator
identity, while physical ownership is the exact key principal.

`POST /v1/quality-review-tasks/:taskId/resume` requires `admin`, a non-empty
reason, a new `evaluator_id`/`evaluator_type` assignment, and a task in
`needs_escalation`; it advances generation and fencing before returning the task
to `pending`. Administrators cannot manually force `resolved`: that state
requires audited upstream Feedback/Outcome correction so the task no longer has
a qualifying signal. See
[quality-review-task-leases.md](./quality-review-task-leases.md).

`GET/POST /v1/retrieval-adjudication-evidence-controls` requires `admin`. POST appends `quarantine` or `restore` for an exact evaluator principal, optional rubric, inclusive `since`, and exclusive/open `until` window. Quarantine may revoke the evaluator key atomically; restore must match the exact window, never un-revokes the key, and never exposes an older adjudication revision. GET can filter by principal/rubric and return only latest exact-window revisions with `effective_only=true`. Evaluation scopes cannot control their own evidence. See [retrieval-adjudication-evidence-controls.md](./retrieval-adjudication-evidence-controls.md).

`POST /v1/quality-datasets` requires the separate `evaluation:export` scope. `rubric_version` and an exclusive ISO-8601 `cutoff` are required; optional evaluator, segment, since, limit, and gate ratio fields constrain materialization. The server selects the latest authenticated adjudications first, then excludes evidence matched by any effective overlapping quarantine. It returns `Cache-Control: no-store`; the manifest includes matching/quarantined control counts and a control fingerprint. `quality-dataset-v1` contains query and Memory content required for local replay, but replaces global identifiers with local aliases or hashes and excludes notes, control reasons, and raw evaluator evidence. Treat the response as restricted data; the API does not persist or manage exported copies. See [retrieval-quality-loop.md](./retrieval-quality-loop.md).

## Memory Cases and Replay Runs

`GET /v1/replay-lab/configuration` and the Memory Case/Replay Run read surfaces accept `evaluation:read`, `evaluation:execute`, `evaluation:export`, or `admin`. Configuration returns the active Context Policy, complete normalized gate defaults including `maximum_unjudged_selected=0`, sensitivity, the default `memory_state_retrieval.v1` scope, both supported scopes, and exact coverage.

`POST /v1/memory-cases` requires `evaluation:execute` and `Idempotency-Key`. Its body contains only `context_id` and `adjudication_id`. The referenced adjudication must be the latest eligible accepted authenticated canonical evidence after effective controls. When the Context has a valid Replay State, the Runtime creates `memory-case-v2` by projecting the complete captured Memory state, observed pool, logical executor, physical-index provenance, labels, and unjudged Memories into local aliases. An older Context without state produces the narrower `memory-case-v1`. `201` creates the Case or adds a distinct source; principal-bound retry semantics are unchanged. List/create responses omit content and state fingerprints, payload, and sources. Full restricted detail requires `evaluation:export` and returns `Cache-Control: no-store`.

`POST /v1/replay-runs` requires `evaluation:execute` and `Idempotency-Key`. It accepts 1-200 unique Case IDs, a candidate Context Policy, optional baseline policy, and partial threshold overrides. The active Runtime Context Policy becomes the baseline when omitted. The Runtime normalizes and persists the complete policies/thresholds, Case-set and configuration hashes, and a lease-fenced job before execution. Long work heartbeats the lease; renewal loss aborts evaluation and prevents stale result/failure commits. The response wraps the current detailed Run under `data.run`; an identical request is replayed with `200` and a new run returns `201`.

Runs use `queued`, `running`, `completed`, `failed`, or `cancelled`. `GET /v1/replay-runs` is a stable page filterable by state/time. `GET /v1/replay-runs/:runId` returns policies, thresholds, coverage, aggregate result, Case summaries, and append-only transition events. Ordered per-Case detail uses only opaque Case identity and local aliases; state-aware results add candidate-pool deltas, candidate misses, unjudged selections, and Oracle coverage. Query/Memory values, raw state, internal replay/dedup identity, and Case content fingerprints are omitted.

`POST /v1/replay-runs/:runId/cancel` requires `evaluation:execute`, accepts an optional bounded reason, changes pending Case results to `skipped`, and returns terminal runs unchanged. `POST /v1/replay-runs/:runId/resume` also requires `evaluation:execute`; it idempotently executes a queued run or reclaims a run whose worker lease expired. It never displaces an unexpired worker, and a terminal run is returned unchanged. See [memory-case-replay.md](./memory-case-replay.md) for integrity, privacy, recovery, and coverage semantics.

`memory_state_retrieval.v1` independently regenerates baseline/candidate pools from the same captured state, then re-evaluates scoring, filters, semantic similarity, and budget. Its coverage is true for the logical candidate index and false for `physical_candidate_index`, extraction, and write policy. The physical ANN identity remains provenance and must be qualified through the real semantic Store benchmark. `frozen_retrieval.v1` remains supported for Case v1 and keeps candidate pool/index coverage false. A Run cannot mix scenarios.

## Replay Cohort Plans and freshness

`GET/POST /v1/replay-cohort-plans` and exact Plan reads accept the Replay read/execute capabilities used by the other Evaluation resources. Creation requires `evaluation:execute` plus `Idempotency-Key`; callers provide rubric, baseline/candidate Memory Policies, thresholds, and a 1-200 Case shard size, but never Case IDs or the cutoff. The Store chooses one database cutoff, resolves the complete eligible latest-authenticated-consensus Write Quality population, and persists the Plan, private member eligibility evidence, stable shards, child Runs, attempts, and append-only events atomically. One Plan is bounded to 10,000 Cases and 100 shards.

List and detail responses are `Cache-Control: no-store` privacy projections. They expose Plan state, population/progress counts, current child Run IDs and summaries, timestamps, and integrity hashes. They omit the member list, Case IDs, source IDs, eligibility snapshots, evaluator principals, Memory content, and adjudication evidence. `POST /:planId/resume` advances bounded unfinished shards without displacing an active lease; `POST /:planId/cancel` cancels pending work and makes the Plan ineligible for release.

`GET /v1/replay-cohort-plans/:planId/freshness` reselects the current same-rubric eligible population inside one Store-consistent snapshot and compares it with the pinned Plan. Optional `maximum_age_seconds` is an integer from 60 through 31,536,000 and defaults to 604,800 (seven days). The response reports `fresh`, `stale`, or `blocked`; evidence age and validity; added/removed/changed/unchanged counts; privacy-safe population/attestation hashes; and `population_current` plus `maximum_evidence_age` gate checks. It never returns the private IDs used to compute those counts. A source/Oracle conflict, empty/over-limit population, population drift, or expired evidence fails closed. This endpoint is an operator preview of the same Store-native check that Release Artifact v12 performs again during construction; an earlier green response is not a reusable authorization token.

## Memory causality

`GET /v1/causal-chains/:entityId` requires `causality:read` or `admin`. Entity type is inferred from a safe ID prefix. `revision` selects an exact non-negative revision; omitting it selects the latest record for that entity. The ledger records Event -> Decision -> Memory revision -> Context -> Feedback/Quality Review Task/Retrieval Adjudication/Outcome, task basis/generation and adjudication completion, adjudication and Outcome revision succession, independent Evidence Controls -> affected Task/Retrieval Adjudication/Outcome eligibility, and exact Outcome/Control -> Guardrail Evaluation -> Rollout Transition evidence.

`direction` is `upstream`, `downstream`, or `both` (default); `depth` is 1-10 (default 3); and `limit` is 1-500 (default 100). Edges are ordered by a decimal Store cursor. Continue with `after=next_cursor` and pass the first response's `snapshot_cursor` on every later page. This fixes the maximum visible edge cursor, so links committed during pagination appear only in a fresh traversal. `has_more` means another edge page exists; `truncated` also becomes true when the requested depth leaves reachable nodes outside the result.

The response is a typed `CausalChainPage` containing a root reference, projected nodes, and projected edges. Entity and relation names are enums, revisions are explicit, and edge provenance is `exact`, `backfilled`, or `inferred`. Attribute maps are relation/type allowlists rather than stored domain payloads. Actor/Workspace IDs, Memory values, prompts, Context queries, Outcome evidence, Evidence Control reasons, credentials, and database details are not returned. Optional `trace_id`/`span_id` fields allow a scoped operator to pivot into an OpenTelemetry backend; their absence does not make the durable causal relation incomplete.

Causal records and links participate in Actor export/purge and current Workspace Snapshot v17. Snapshot v5 introduced this causal collection; v11 added Case/Run evidence, v12 added the hashed Case idempotency ledger, v13 added Memory State Snapshot/Context binding collections, and v17 adds Replay Cohort Plan evidence. Causality projections do not expose State payloads or content fingerprints. The CLI surface is `memplumb explain --key ENTITY_ID`. See [observability.md](./observability.md) for the distinction between durable evidence and best-effort Span Links.

## Outcome evidence

`POST /v1/context/:contextId/outcomes` requires `Idempotency-Key` and `evaluation:write`. In managed-auth mode, the API key must be evaluator-bound. The server derives evaluator ID/type from that key; optional identity fields in the body are assertions and must match. The first request atomically commits the immutable Outcome, its key hash/principal-bound request hash, and Change Feed event; an identical replay returns the original Outcome with `200` and `meta.idempotent_replay=true`; key reuse by another principal, context, or payload returns `409 IDEMPOTENCY_KEY_REUSED`. The raw key is never stored. The server derives actor, release, and Context Policy attribution from the referenced run and assigns an immutable revision. Responses expose `evaluator_authentication` and the non-secret `evaluator_principal_id` for audit. When a canary has an Outcome guardrail, only authenticated matching evidence can affect `meta.rollout`. `GET /v1/outcomes` filters raw records; `GET /v1/outcomes/compare` reports descriptive statistics across both authenticated and unverified evidence. Reads require `evaluation:read`. See [outcome-ledger.md](./outcome-ledger.md).

`GET/POST /v1/outcome-evidence-controls` requires `admin`. POST appends `quarantine` or `restore` for an exact evaluator-principal window and may atomically revoke the key during quarantine. Identical transitions return `200`; new revisions return `201`. The response includes the durable control and current rollout reconciliation. See [outcome-evidence-controls.md](./outcome-evidence-controls.md).

## Change Feed

`GET /v1/changes` returns ordered Workspace changes after the optional decimal-string `after` cursor. `limit` is capped at 1000 and repeated `type` parameters filter change types. The response contains `items`, `next_cursor`, `has_more`, `retention_floor`, and `cursor_expired`. Delivery is at-least-once; use stable change IDs for deduplication.

Named consumer endpoints under `/v1/change-consumers` persist checkpoints, lease ownership, expiry, and exact pending counts. `claim`, `commit`, and `release` require `memory:read`; deletion requires `admin`. Lease contention, stale ownership, cursor regression, expired cursors, and retention conflicts return stable 4xx errors. See [change-feed.md](./change-feed.md) for the takeover protocol and disaster-recovery semantics.

## Durable Webhooks

Admin-scoped endpoints under `/v1/webhooks` create, inspect, pause/resume, rotate, and delete signed endpoints. `/v1/webhook-deliveries` exposes per-attempt state and dead-letter redrive; `/v1/webhook-stats` returns queue totals. Create/rotate returns the signing secret once. Other responses never expose plaintext or encrypted secrets. See [webhooks.md](./webhooks.md) for signature, retry, SSRF, and at-least-once semantics.

## Stable error codes

- `UNAUTHORIZED`
- `FORBIDDEN`
- `RATE_LIMITED`
- `INVALID_REQUEST`
- `INVALID_JSON`
- `PAYLOAD_TOO_LARGE`
- `IDEMPOTENCY_KEY_REQUIRED`
- `INVALID_IDEMPOTENCY_KEY`
- `IDEMPOTENCY_KEY_REUSED`
- `EVALUATOR_BINDING_REQUIRED`
- `EVALUATOR_IDENTITY_REQUIRED`
- `EVALUATOR_IDENTITY_MISMATCH`
- `INVALID_RETRIEVAL_ADJUDICATION`
- `ADJUDICATION_CONTEXT_MISMATCH`
- `ADJUDICATION_CANDIDATE_REVISION_MISMATCH`
- `ADJUDICATION_DANGLING_MISSED_POSITIVE`
- `ADJUDICATION_LABEL_CONFLICT`
- `INVALID_QUALITY_REVIEW_LEASE`
- `INVALID_QUALITY_REVIEW_LEASE_TOKEN`
- `INVALID_QUALITY_REVIEW_QUEUE`
- `INVALID_QUALITY_REVIEW_SIGNALS`
- `INVALID_QUALITY_REVIEW_STATUS`
- `INVALID_QUALITY_REVIEW_TASK`
- `INVALID_QUALITY_REVIEW_TASK_EVENT`
- `INVALID_QUALITY_REVIEW_TASK_STATE`
- `QUALITY_REVIEW_CLAIM_REPLAY_EXPIRED`
- `QUALITY_REVIEW_CONTEXT_NOT_FOUND`
- `QUALITY_REVIEW_EVALUATOR_NOT_AVAILABLE`
- `QUALITY_REVIEW_IDEMPOTENCY_CONFLICT`
- `QUALITY_REVIEW_TASK_ASSIGNMENT_CONFLICT`
- `QUALITY_REVIEW_TASK_COMPLETION_REQUIRED`
- `QUALITY_REVIEW_TASK_CONTEXT_NOT_FOUND`
- `QUALITY_REVIEW_TASK_LEASE_HELD`
- `QUALITY_REVIEW_TASK_LEASE_INVALID`
- `QUALITY_REVIEW_TASK_FILTER_MISMATCH`
- `QUALITY_REVIEW_TASK_FORCE_REQUIRED`
- `QUALITY_REVIEW_TASK_NOT_ESCALATED`
- `QUALITY_REVIEW_TASK_NOT_FOUND`
- `QUALITY_REVIEW_TASK_NOT_PENDING`
- `QUALITY_REVIEW_TASK_OWNER_MISMATCH`
- `QUALITY_REVIEW_TASK_RESUME_REQUIRED`
- `QUALITY_REVIEW_TASK_STALE`
- `QUALITY_DATASET_CUTOFF_VIOLATION`
- `QUALITY_DATASET_INTEGRITY_MISMATCH`
- `QUALITY_DATASET_UNAUTHENTICATED_EVIDENCE`
- `MEMORY_CASE_SOURCE_NOT_FOUND`
- `MEMORY_CASE_SOURCE_INELIGIBLE`
- `MEMORY_CASE_NOT_FOUND`
- `INVALID_REPLAY_INPUT`
- `REPLAY_RUN_NOT_FOUND`
- `REPLAY_CASE_RESULT_NOT_FOUND`
- `REPLAY_RUN_NOT_CLAIMABLE`
- `REPLAY_RUN_STALE_LEASE`
- `INVALID_EVIDENCE_CONTROL`
- `EVALUATOR_PRINCIPAL_NOT_FOUND`
- `EVIDENCE_WINDOW_NOT_QUARANTINED`
- `EVIDENCE_CONTROL_ALREADY_APPLIED`
- `INVALID_CAUSAL_ENTITY`
- `INVALID_CAUSAL_QUERY`
- `CAUSAL_ROOT_NOT_FOUND`
- `REQUEST_IN_PROGRESS`
- `ROUTE_NOT_FOUND`
- `INTERNAL_ERROR`
- `EMBEDDING_PROVIDER_FAILED`
- `SEMANTIC_INDEX_NOT_READY`
- `SEMANTIC_INDEX_UNAVAILABLE`
- `NOT_READY`
- `DEPENDENCY_UNAVAILABLE`
- `CHANGE_CONSUMER_LEASE_HELD`
- `CHANGE_CONSUMER_NOT_OWNED`
- `CHANGE_CONSUMER_NOT_FOUND`
- `CHANGE_CURSOR_REGRESSION`
- `CHANGE_CURSOR_AHEAD`
- `CHANGE_CURSOR_EXPIRED`
- `CHANGE_CONSUMERS_LAGGING`
- `WEBHOOK_KEY_UNAVAILABLE`
- `WEBHOOK_NOT_FOUND`
- `INVALID_WEBHOOK_URL`
- `WEBHOOK_PRIVATE_TARGET`
- `WEBHOOK_DNS_FAILED`
- `WEBHOOK_DELIVERY_NOT_OWNED`
- `WEBHOOK_DELIVERY_NOT_DEAD`

The machine-readable OpenAPI 1.9.1 contract is [openapi.yaml](./openapi.yaml).
