Skip to main content

CPI & Feedback

The Compliance Performance Index (CPI) is the long-running quality signal Adjudon tracks across an org's decision history: how often a flagged trace was correctly flagged, how often a human reviewer corrected the agent, and how those rates evolve over time. The endpoints in this resource expose two reads (the dashboard CPI rollup and the per-feedback analytics) and one write (POST /ingest) that lets external systems — a customer's own GRC tool, a downstream review pipeline, an internal Slack workflow — submit human verdicts back into the trace. The ingested verdict updates the trace's humanOverride flag, rewrites the decision under outputDecision, and re-indexes the trace into vector memory so future similar-search queries reach the corrected decision rather than the original. Tested against API version v1. Mounted at /api/v1/cpi.

Two surfaces, two auth chains

The reads and the write live on different auth chains because the consumer pattern is different: dashboard reads come from a human-authenticated JWT; feedback ingest comes from a machine-to-machine API key on a downstream pipeline.

EndpointAuthPlan gate
GET /api/v1/cpiJWT (dashboard)none
GET /api/v1/cpi/analyticsJWT (dashboard)none
POST /api/v1/cpi/ingestadj_live_* API keycpiFeedbackIngest (Enterprise+)

Both reads carry their own cpiRateLimit (60 / 15 min per user) because the dashboard polls them on every Overview page load.

Endpoints

GET   /api/v1/cpi
GET /api/v1/cpi/analytics
POST /api/v1/cpi/ingest

Get the CPI dashboard rollup

GET /api/v1/cpi

Returns the org's compliance-performance scoreboard: cumulative correct-flag rate, false-flag rate, human-override rate, average confidence per status band, and the rolling 30-day trend the dashboard's CPI page renders. Reads only; no plan gate.

curl
curl https://api.adjudon.com/api/v1/cpi \
-H "Authorization: Bearer $ADJUDON_JWT"

Errors: 401, 429 RATE_LIMITED, 500.

Get feedback analytics

GET /api/v1/cpi/analytics

Returns the corrections breakdown: per-agent override rate, top reasons for correction (parsed from feedbackNotes), confidence delta between original and corrected decisions, and the time-to-correction histogram. Drives the dashboard's Feedback Analytics page; same auth + rate-limit posture as the rollup.

Ingest feedback for a trace

POST /api/v1/cpi/ingest
Body fieldRequiredDescription
traceIdyesThe trace the feedback applies to
actionnoapprove or correct; correct is the implicit default when correctedDecision is set
correctedDecisionconditionalRequired when action !== 'approve'; the human's overriding decision
feedbackNotesnoFree-text rationale; persisted on the trace and surfaced in analytics
userEmailnoEmail of the human reviewer; persisted as metadata.approvedBy for audit attribution

Two action paths:

  • approve stamps humanOverride: false, status: 'success', and writes metadata.approvedBy + metadata.approvedAt. The agent's original decision stands; the human verdict confirms it.
  • correct stamps humanOverride: true, status: 'success', rewrites outputDecision with the corrected payload, preserves the original under outputDecision.originalDecision, and prepends the rationale with [HUMAN CORRECTION: ...].
curl — correct a flagged decision
curl -X POST https://api.adjudon.com/api/v1/cpi/ingest \
-H "X-API-Key: adj_live_..." \
-H "Content-Type: application/json" \
-d '{
"traceId": "trace_8f7d6c5b...",
"action": "correct",
"correctedDecision": "Approved refund of EUR 24.99 to original payment method",
"feedbackNotes": "Original recommendation declined a valid policy claim",
"userEmail": "[email protected]"
}'

The handler does five things atomically:

  1. Fetches the current trace state (scoped to caller's org; cross-tenant lookup is impossible).
  2. Builds the $set update for approve or correct.
  3. Writes the update via findOneAndUpdate (no read-modify- write race).
  4. Re-indexes the trace into vector memory (fire-and-forget) so future similarity searches return the corrected decision.
  5. Writes one cpi.approved or cpi.corrected audit-log entry with the user email and rationale in detail.

Errors: 400 VALIDATION_ERROR (missing traceId; missing correctedDecision on a non-approve action), 401, 403 UPGRADE_REQUIRED (plan), 404 NOT_FOUND (cross-tenant or nonexistent traceId), 500.

Why the corrected decision is preserved

Compliance is not a one-shot judgement — an auditor reading the trace six months later needs to see both what the agent originally decided and what the human ultimately landed on. The handler preserves both:

outputDecision.decision           ← what the human says now
outputDecision.originalDecision ← what the agent said before
outputDecision.correctionNotes ← the rationale text
rationale ← prefixed with [HUMAN CORRECTION: ...]

The original decision is never overwritten silently. If a regulator challenges "why did the system change its mind on this case", the trace document carries the answer in the same payload shape as every other trace.

Where the ingested feedback flows downstream

External CPI ingestion is the closing edge of three feedback loops the platform runs continuously:

  1. Auto-Approval pattern maturation. Each approve or correct action against a previously-flagged trace contributes to the observation count on any matching ApprovalPattern. 50+ observations with a 95%+ approval rate is what graduates a pattern from observing to pending_signoff.
  2. Decision Mining. The dashboard's mining surface looks for clusters of corrections sharing a feedbackNotes pattern; recurring corrections against the same agent + condition are the candidates for new policy proposals.
  3. CPI rollup. Every ingested verdict updates the correct-flag and false-flag rates the dashboard renders on GET /api/v1/cpi; the trend is what tells an operator whether the agent's calibration is improving or drifting.

The ingestion endpoint is one POST; the ripple-effects above run asynchronously in the background and surface on the dashboard within seconds.

Common gotchas

  • Plan gate is on the WRITE only. Reads (GET / and GET /analytics) are open to any JWT-authenticated user on any plan. Only POST /ingest requires cpiFeedbackIngest (Enterprise+). This is deliberate: the CPI rollup is a cross-cutting compliance signal that all paid customers see.
  • approve vs correct semantics. Both end with status: 'success', but humanOverride flips only on correct. The Feedback Analytics page surfaces correction rates off this flag.
  • Cross-tenant lookup is impossible. The handler scopes every findOne and findOneAndUpdate to req.organizationId; a traceId from another org returns 404 NOT_FOUND, never the cross-tenant content.
  • Re-indexing is fire-and-forget. Vector memory re-indexing for the corrected decision runs after the response. A similarity search a few seconds later may still return the pre-correction representation; reads from MongoDB always reflect the corrected state.
  • Rate limit on reads, none on the write. cpiRateLimit applies to the dashboard reads; the write goes through the per-agent and per-org limits on the API-key chain instead.
  • Idempotency. Idempotency-Key middleware is wired only on POST /traces. POSTing the same correction twice for the same traceId is harmless — the second call rewrites the trace to the same state.

See also

  • Traces API — the source-of-truth this resource mutates
  • Reviews API — the in-platform alternative for human review (this resource is the external feedback channel)
  • Audit Log API — where every cpi.approved and cpi.corrected event lands
  • Plans & Features — the cpiFeedbackIngest feature gate
  • Error Codes — the broader error taxonomy