Traces
A DecisionTrace is the per-decision record an AI agent submits to
Adjudon. Every integration begins here: the trace describes one AI
decision, the Confidence Engine scores it, the Policy Engine gates
it, and the Decision Hash Chain anchors it. Tested against API
version v1. Authentication on the ingestion endpoint is API-key
(adj_live_* or adj_agent_*); the dashboard-side endpoints take a
JWT.
The DecisionTrace object
| Field | Type | Required | Description |
|---|---|---|---|
traceId | string | yes | Server-generated per-trace identifier |
organizationId | string | yes | Owning org (set from auth) |
workspaceId | string | no | Workspace scope when the key is workspace-scoped |
agentId | string | yes | The AI agent that produced this decision |
agentVersion | string | no | Version stamp of the agent code |
schemaVersion | string | yes | Trace schema version; defaults to the latest published |
inputContext | object | yes | What the agent saw; inputContext.prompt is required |
outputDecision | object | yes | What the agent decided; outputDecision.action is required |
rationale | string | no | Free-text explanation; indexed for search |
alternatives | Alternative[] | no | Other decisions considered ({ decision, confidence }) |
triggeringCondition | string | no | What event prompted the agent to act |
status | enum | no | One of success, approved, flagged, escalated, pending, blocked |
confidenceScore | number | no | Engine-computed score, 0.0–1.0 |
tags | string[] | no | Engine tags: LOW_CONFIDENCE, HIGH_AMBIGUITY, etc. |
humanOverride | boolean | no | true if a reviewer overrode the engine verdict |
metadata | object | no | Free-form caller-supplied metadata |
timestamp | string (ISO 8601) | no | When the agent made the decision; defaults to ingestion time |
clientInfo | object | no | Auto-parsed from User-Agent; { sdk, sdkVersion, runtime, os } |
hashChain | object | no | Back-reference: { sequence, chainHash } once the chain row lands |
A small number of Phase-1 fields (adapter, mlBomReference,
art12, conversation, crew) are populated by Adjudon's native
SDK adapters and the CycloneDX 1.7 ML-BOM pipeline. They are
documented per-feature on the corresponding pages and ignored by the
core ingestion path if absent.
Endpoints
POST /api/v1/traces
GET /api/v1/traces
GET /api/v1/traces/stream
GET /api/v1/traces/export
GET /api/v1/traces/:id
GET /api/v1/traces/:id/ml-bom
POST /api/v1/traces/memory
POST /api/v1/traces/bulk-status
POST /api/v1/traces/bulk-delete
The ingestion endpoint takes an API key; every other endpoint takes a
dashboard JWT. bulk-delete additionally requires the admin or
owner role.
Ingest a trace
POST /api/v1/traces
The single core call. Auth via API key; the response status carries
product semantics: 201 (approved), 202 (flagged for review), or
403 (blocked by policy).
| Body field | Required | Description |
|---|---|---|
agentId | yes | Non-empty string identifying the agent |
inputContext.prompt | yes | The prompt the agent reasoned over |
outputDecision.action | yes | The decided action (string or object) |
outputDecision.rationale | no | Free-text explanation for audit |
triggeringCondition | no | What initiated the call |
metadata | no | Caller-supplied free-form context |
schemaVersion | no | Defaults to the latest published version |
Pass an Idempotency-Key header so retries do not produce duplicates;
Adjudon auto-generates one from the request hash if absent.
curl -X POST https://api.adjudon.com/api/v1/traces \
-H "Authorization: Bearer $ADJUDON_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"agentId": "underwriter-v1",
"inputContext": { "prompt": "Loan: 50000 EUR, 36 months" },
"outputDecision": { "action": "deny", "rationale": "DTI ratio above policy" }
}'
from adjudon import Adjudon
import os, uuid
client = Adjudon(api_key=os.environ["ADJUDON_API_KEY"])
trace = client.traces.ingest(
agent_id="underwriter-v1",
input_context={"prompt": "Loan: 50000 EUR"},
output_decision={"action": "deny", "rationale": "DTI ratio above policy"},
idempotency_key=str(uuid.uuid4()),
)
import { Adjudon } from "@adjudon/node";
import { randomUUID } from "node:crypto";
const client = new Adjudon({ apiKey: process.env.ADJUDON_API_KEY });
const trace = await client.traces.ingest({
agentId: "underwriter-v1",
inputContext: { prompt: "Loan: 50000 EUR" },
outputDecision: { action: "deny", rationale: "DTI ratio above policy" },
idempotencyKey: randomUUID(),
});
String body = """
{ "agentId": "underwriter-v1",
"inputContext": { "prompt": "Loan: 50000 EUR" },
"outputDecision": { "action": "deny" } }""";
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create("https://api.adjudon.com/api/v1/traces"))
.header("Authorization", "Bearer " + System.getenv("ADJUDON_API_KEY"))
.header("Content-Type", "application/json")
.header("Idempotency-Key", UUID.randomUUID().toString())
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
Response (201 Created, approved):
{
"success": true,
"data": {
"traceId": "trace_aBcD1234",
"agentId": "underwriter-v1",
"status": "approved",
"confidenceScore": 0.83,
"tags": [],
"matchedPolicy": null,
"createdAt": "2026-05-06T10:14:22.317Z"
}
}
202 returns the same shape with status: "flagged" and a
matchedPolicy.name. 403 returns the failure envelope with code
ADJ_BLOCKED_BY_POLICY. Errors: 400 ADJ_VALIDATION_FAILED,
400 ADJ_UNKNOWN_SCHEMA_VERSION, 401, 403 plan-gate or block,
429 RATE_LIMIT_EXCEEDED, 500 INTERNAL_ERROR.
List traces
Page-based pagination, dashboard auth.
GET /api/v1/traces
| Query parameter | Default | Description |
|---|---|---|
page | 1 | 1-indexed page number |
limit | 25 | Page size; capped server-side |
status | — | Filter by status enum |
agentId | — | Filter by agent |
humanOverride | — | true / false |
minConfidence / maxConfidence | — | Range filter, 0.0–1.0 |
dateFrom / dateTo | — | ISO date range on timestamp |
search | — | Full-text search on rationale and triggeringCondition |
curl "https://api.adjudon.com/api/v1/traces?status=flagged&page=1&limit=25" \
-H "Authorization: Bearer $ADJUDON_API_KEY"
Response shape:
{
"success": true,
"data": [/* DecisionTrace[] */],
"pagination": { "page": 1, "limit": 25, "total": 1247, "pages": 50, "hasMore": true }
}
The List endpoint uses page + limit. List endpoints elsewhere
in the API (e.g., /api/v1/hash-chain/entries) use cursor pagination
on beforeSeq; the difference is documented per-resource. Errors:
401, 403, 500 INTERNAL_ERROR.
Get one trace
GET /api/v1/traces/:id
Returns the full DecisionTrace. :id is the Mongo ObjectId (not
the traceId string). Errors: 401, 404 NOT_FOUND, 500.
Stream traces (SSE)
GET /api/v1/traces/stream?stream_token=...
Server-Sent Events stream of new traces in the workspace. Auth via
short-lived single-use stream token (preferred) or a query-string JWT
fallback. The browser must handle the SSE event: error payload that
arrives if the token is invalid or expired (INVALID_STREAM_TOKEN).
Export traces (CSV)
GET /api/v1/traces/export
CSV export of the same query the List endpoint accepts. Plan-gated by
the export feature. Errors: 401, 403 UPGRADE_REQUIRED, 500.
Vector memory search
POST /api/v1/traces/memory
Vector-similarity search over historic traces. Plan-gated by the
memorySearch feature. Body: { query, minScore?, topK? }. Errors:
400 VALIDATION_ERROR, 401, 403, 500.
Bulk operations
POST /api/v1/traces/bulk-status updates status on a list of trace
ids. POST /api/v1/traces/bulk-delete is admin/owner-only and
soft-deletes traces by id list (chain entries remain by Cardinal Rule
5; payload fields are nullified). Errors: 400 INVALID_IDS, 400
INVALID_STATUS, 401, 403, 500.
ML-BOM (CycloneDX 1.7)
GET /api/v1/traces/:id/ml-bom
Returns the CycloneDX 1.7 ML-BOM for the agent that produced this
trace, with the BOM digest and URN. Phase-1 feature; on agents
without an ML-BOM record, returns 404 NOT_FOUND. Errors: 401,
404, 500.
Common gotchas
- Plan-gates.
traces/exportrequires theexportfeature;traces/memoryrequiresmemorySearch. Sandbox returns403 UPGRADE_REQUIREDon both. - Idempotency. Auto-generated keys are deterministic on the
request payload — a second POST with identical body returns the
cached response, not a new trace. Pass an explicit
Idempotency-Keyif you want explicit retry control. - Status semantics on the response code.
201≠202≠403— the body shape is the same, the status code carries the policy verdict. Branch on the status code, not on the body. :idis the Mongo_id, nottraceId. The two are different identifiers. List responses include both.
See also
- Audit Log & Security — the chain row each trace anchors
- Traces & Confidence Scoring — the three pillars in detail
- Hash Chain API — verify and export the chain
- Error Codes — the full error taxonomy
- Quickstart — record your first trace
- Idempotency — the
Idempotency-Keyheader - Tracing a multi-step agent — conversation + parent-trace patterns