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.
| Endpoint | Auth | Plan gate |
|---|---|---|
GET /api/v1/cpi | JWT (dashboard) | none |
GET /api/v1/cpi/analytics | JWT (dashboard) | none |
POST /api/v1/cpi/ingest | adj_live_* API key | cpiFeedbackIngest (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 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 field | Required | Description |
|---|---|---|
traceId | yes | The trace the feedback applies to |
action | no | approve or correct; correct is the implicit default when correctedDecision is set |
correctedDecision | conditional | Required when action !== 'approve'; the human's overriding decision |
feedbackNotes | no | Free-text rationale; persisted on the trace and surfaced in analytics |
userEmail | no | Email of the human reviewer; persisted as metadata.approvedBy for audit attribution |
Two action paths:
approvestampshumanOverride: false,status: 'success', and writesmetadata.approvedBy+metadata.approvedAt. The agent's original decision stands; the human verdict confirms it.correctstampshumanOverride: true,status: 'success', rewritesoutputDecisionwith the corrected payload, preserves the original underoutputDecision.originalDecision, and prepends the rationale with[HUMAN CORRECTION: ...].
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:
- Fetches the current trace state (scoped to caller's org; cross-tenant lookup is impossible).
- Builds the
$setupdate forapproveorcorrect. - Writes the update via
findOneAndUpdate(no read-modify- write race). - Re-indexes the trace into vector memory (fire-and-forget) so future similarity searches return the corrected decision.
- Writes one
cpi.approvedorcpi.correctedaudit-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:
- Auto-Approval pattern maturation. Each
approveorcorrectaction 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 fromobservingtopending_signoff. - Decision Mining. The dashboard's mining surface looks
for clusters of corrections sharing a
feedbackNotespattern; recurring corrections against the same agent + condition are the candidates for new policy proposals. - CPI rollup. Every ingested verdict updates the
correct-flagandfalse-flagrates the dashboard renders onGET /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.