Skip to main content

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

FieldTypeRequiredDescription
traceIdstringyesServer-generated per-trace identifier
organizationIdstringyesOwning org (set from auth)
workspaceIdstringnoWorkspace scope when the key is workspace-scoped
agentIdstringyesThe AI agent that produced this decision
agentVersionstringnoVersion stamp of the agent code
schemaVersionstringyesTrace schema version; defaults to the latest published
inputContextobjectyesWhat the agent saw; inputContext.prompt is required
outputDecisionobjectyesWhat the agent decided; outputDecision.action is required
rationalestringnoFree-text explanation; indexed for search
alternativesAlternative[]noOther decisions considered ({ decision, confidence })
triggeringConditionstringnoWhat event prompted the agent to act
statusenumnoOne of success, approved, flagged, escalated, pending, blocked
confidenceScorenumbernoEngine-computed score, 0.0–1.0
tagsstring[]noEngine tags: LOW_CONFIDENCE, HIGH_AMBIGUITY, etc.
humanOverridebooleannotrue if a reviewer overrode the engine verdict
metadataobjectnoFree-form caller-supplied metadata
timestampstring (ISO 8601)noWhen the agent made the decision; defaults to ingestion time
clientInfoobjectnoAuto-parsed from User-Agent; { sdk, sdkVersion, runtime, os }
hashChainobjectnoBack-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 fieldRequiredDescription
agentIdyesNon-empty string identifying the agent
inputContext.promptyesThe prompt the agent reasoned over
outputDecision.actionyesThe decided action (string or object)
outputDecision.rationalenoFree-text explanation for audit
triggeringConditionnoWhat initiated the call
metadatanoCaller-supplied free-form context
schemaVersionnoDefaults 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
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" }
}'
Python
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()),
)
Node
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(),
});
Java
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 parameterDefaultDescription
page11-indexed page number
limit25Page size; capped server-side
statusFilter by status enum
agentIdFilter by agent
humanOverridetrue / false
minConfidence / maxConfidenceRange filter, 0.0–1.0
dateFrom / dateToISO date range on timestamp
searchFull-text search on rationale and triggeringCondition
curl
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.

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/export requires the export feature; traces/memory requires memorySearch. Sandbox returns 403 UPGRADE_REQUIRED on 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-Key if you want explicit retry control.
  • Status semantics on the response code. 201202403 — the body shape is the same, the status code carries the policy verdict. Branch on the status code, not on the body.
  • :id is the Mongo _id, not traceId. The two are different identifiers. List responses include both.

See also