Traces
The audit trail: what one agent did inside an execution, and every sub-agent call it started.
Overview
A trace is project-scoped: the platform writes it as a side effect of a real execution, and the record itself is never deleted, because it is the audit trail the platform sells. Its content — the step payload — can be purged on request; see Purging trace content below. A trace's children are the traces its sub-agent calls started, forming a tree; each Generation inside a trace is one model loop.
See the OpenAPI spec for the full endpoint and schema reference, or browse it rendered under API Reference → Traces.
Data Model
Trace
| Field | Type | Description |
|---|---|---|
id | string | Public trace ID. |
project_id | string | The owning project. |
agent_id | string | The agent this trace belongs to. |
parent_trace_id | string, nullable | The trace that started this one, for sub-agent calls. |
root_trace_id | string, nullable | The top-level trace of this execution tree. |
step_count | integer | Number of recorded steps. |
has_steps | boolean | Whether the full step payload (tool calls, inputs/outputs, cost) has been saved and is fetchable — see Reading a trace's steps below. |
error | object, nullable | Structured error if the trace failed. |
content_redacted_at | string (date-time), nullable | When this trace's content was purged — see Purging trace content. Null when nothing has been purged. |
created_at | string (date-time) |
TraceSteps
| Field | Type | Description |
|---|---|---|
trace_id | string, nullable | The trace these steps belong to. |
steps | array of object | One entry per step, oldest first, exactly as the runtime recorded it — tool calls, their arguments, results and cost. |
Key Concepts
Trace ancestry
GET /v1/projects/{project_id}/traces/{trace_id}/tree
returns the full ancestry tree rooted at a trace — every child trace a
sub-agent call started, recursively.
Generations within a trace
GET /v1/projects/{project_id}/traces/{trace_id}/generations
lists every generation recorded under a trace.
Reading a trace's steps
GET /v1/projects/{project_id}/traces/{trace_id}/steps
returns the step-by-step record of what the agent actually did: every tool
call, its arguments, its result, and its cost. steps is passed through as
the runtime recorded it rather than reshaped into a fixed schema, since its
shape belongs to the model-loop runtime, not to this API.
Saving the payload is fire-and-forget on the runtime, so calling this
immediately after a generation finishes can 404 with steps_not_available
for a brief window even though the trace itself already exists — retry once
has_steps on the trace is true.
A trace with no payload at all answers steps_redacted instead: its content
was purged, or its agent runs in
zero-retention and nothing was ever
written. The two codes exist so a polling client can tell them apart —
retrying steps_not_available succeeds once the write lands, and retrying
steps_redacted never does. The response carries content_redacted_at in
details, the same marker the trace itself reports.
Purging trace content
DELETE /v1/projects/{project_id}/traces/{trace_id}/content
deletes the trace's step payload and clears its recorded error, cascading to
every descendant trace in its execution tree and to their generations. The
rows survive as auditable skeletons — ids, timestamps and step counts are
preserved — with content_redacted_at set as verifiable proof the content is
gone. A purged trace still reads back with
GET /v1/projects/{project_id}/traces/{trace_id};
a 404 there would prove nothing about what was erased.
The call is idempotent: purging an already-purged trace succeeds and leaves
the original content_redacted_at in place.
Examples
- CLI
- SDK
- curl
naturali get-trace \
--project-id proj_V1StGXR8Z5jdHi6B \
--trace-id trace_V1StGXR8Z5jdHi6B
const { data: trace } = await naturali.traces.getTrace({
path: {
project_id: 'proj_V1StGXR8Z5jdHi6B',
trace_id: 'trace_V1StGXR8Z5jdHi6B',
},
});
curl https://api.naturali.ai/v1/projects/proj_V1StGXR8Z5jdHi6B/traces/trace_V1StGXR8Z5jdHi6B \
-H "Authorization: Bearer $NATURALI_API_KEY"
Reading the steps
- CLI
- SDK
- curl
naturali get-trace-steps \
--project-id proj_V1StGXR8Z5jdHi6B \
--trace-id trace_V1StGXR8Z5jdHi6B
const { data: steps } = await naturali.traces.getTraceSteps({
path: {
project_id: 'proj_V1StGXR8Z5jdHi6B',
trace_id: 'trace_V1StGXR8Z5jdHi6B',
},
});
curl https://api.naturali.ai/v1/projects/proj_V1StGXR8Z5jdHi6B/traces/trace_V1StGXR8Z5jdHi6B/steps \
-H "Authorization: Bearer $NATURALI_API_KEY"
Purging content
- CLI
- SDK
- curl
naturali purge-trace-content \
--project-id proj_V1StGXR8Z5jdHi6B \
--trace-id trace_V1StGXR8Z5jdHi6B
const { data: purged } = await naturali.traces.purgeTraceContent({
path: {
project_id: 'proj_V1StGXR8Z5jdHi6B',
trace_id: 'trace_V1StGXR8Z5jdHi6B',
},
});
curl -X DELETE https://api.naturali.ai/v1/projects/proj_V1StGXR8Z5jdHi6B/traces/trace_V1StGXR8Z5jdHi6B/content \
-H "Authorization: Bearer $NATURALI_API_KEY"