Erase a person's data
Someone asks you to delete what your agent recorded about them. By the end of this tutorial you will have destroyed that content and be able to prove it — while the run still appears in your audit trail and still prices on your bill.
That combination is the point. Deleting the rows would satisfy the request and wreck your books; keeping them would preserve your books and fail the request. Neither is what you want, so neither is what this does.
Three steps:
Each step is one API call, shown for all three clients. The ids in the responses are examples — copy the ones your own calls return.
Prerequisites
-
A credential. A
nat_sk_…API key (or a session JWT from Auth) exported asNATURALI_TOKEN, and your client set up — the CLI, the SDK or plaincurlagainsthttps://api.naturali.ai/v1:export NATURALI_TOKEN=nat_sk_...export NATURALI_API=https://api.naturali.ai/v1 # curl examples only -
An agent that has run at least once. That is what Your first agent generation builds — do it first if you haven't. Arrive here with both ids exported:
export PROJECT=proj_V1StGXR8Z5jdHi6Bexport AGENT=agent_V1StGXR8Z5jdHi6Bexport NATURALI_PROJECT=$PROJECT # lets the CLI omit --project-id
1. Find the run to erase
An erasure request names a person, not an id, so start from the runs that person's conversation produced. List the agent's generations and pick the one to erase.
- CLI
- SDK
- curl
naturali list-agent-generations \
--agent-id agent_V1StGXR8Z5jdHi6B \
--limit 5
const { data: page } = await naturali.generations.listAgentGenerations({
path: { project_id: PROJECT, agent_id: AGENT },
query: { limit: 5 },
});
curl "$NATURALI_API/projects/$PROJECT/agents/$AGENT/generations?limit=5" \
-H "Authorization: Bearer $NATURALI_TOKEN"
{
"data": [
{
"id": "gen_V1StGXR8Z5jdHi6B",
"trace_id": "trace_V1StGXR8Z5jdHi6B",
"status": "completed",
"stop_reason": "stop",
"action_id": "support.triage",
"metadata": { "ticket_id": "ZD-4821" },
"content_redacted_at": null,
"created_at": "2026-07-14T09:00:00.000Z"
}
],
"total": 1,
"limit": 5,
"offset": 0
}
content_redacted_at: null means nothing has been erased from this record yet.
It is the field the last step turns into your proof.
Take the trace_id, not the generation id — the next step explains why:
export TRACE=trace_V1StGXR8Z5jdHi6B
2. Purge its content
DELETE /v1/projects/{project_id}/traces/{trace_id}/content
destroys the trace's step payload — every tool call, its
arguments and its results — and clears the recorded error. It cascades to every
descendant trace in the execution tree and to all of their generations, so one
call covers the whole run including anything a sub-agent did.
- CLI
- SDK
- curl
naturali purge-trace-content \
--trace-id trace_V1StGXR8Z5jdHi6B
const { data: purged } = await naturali.traces.purgeTraceContent({
path: { project_id: PROJECT, trace_id: TRACE },
});
curl -X DELETE "$NATURALI_API/projects/$PROJECT/traces/$TRACE/content" \
-H "Authorization: Bearer $NATURALI_TOKEN"
{
"id": "trace_V1StGXR8Z5jdHi6B",
"project_id": "proj_V1StGXR8Z5jdHi6B",
"agent_id": "agent_V1StGXR8Z5jdHi6B",
"parent_trace_id": null,
"root_trace_id": "trace_V1StGXR8Z5jdHi6B",
"step_count": 4,
"has_steps": false,
"error": null,
"content_redacted_at": "2026-07-20T11:04:11.238Z",
"created_at": "2026-07-14T09:00:00.000Z"
}
has_steps has flipped to false and content_redacted_at is stamped. Note
what did not change: step_count still says 4. The record that four
things happened survives; what they were does not.
There is a second, narrower call —
DELETE /v1/projects/{project_id}/generations/{generation_id}/content
— that clears one generation's metadata, error, extraction and the
internal recovery state of a paused run. It is the right tool when a request
names a single model turn.
It is the wrong tool here. It does not touch the parent trace's step payload, and that payload holds the same run's tool arguments and results. Reach for the generation purge when you mean one turn; reach for the trace purge, as this step does, when you mean a run.
3. Prove the content is gone
An erasure you cannot demonstrate is not much use when someone asks you to show your work. Read the generation back.
- CLI
- SDK
- curl
naturali get-generation \
--generation-id gen_V1StGXR8Z5jdHi6B
const { data: generation } = await naturali.generations.getGeneration({
path: { project_id: PROJECT, generation_id: 'gen_V1StGXR8Z5jdHi6B' },
});
curl "$NATURALI_API/projects/$PROJECT/generations/gen_V1StGXR8Z5jdHi6B" \
-H "Authorization: Bearer $NATURALI_TOKEN"
{
"id": "gen_V1StGXR8Z5jdHi6B",
"trace_id": "trace_V1StGXR8Z5jdHi6B",
"status": "completed",
"stop_reason": "stop",
"action_id": "support.triage",
"trigger_id": null,
"extraction": null,
"metadata": null,
"content_redacted_at": "2026-07-20T11:04:11.238Z",
"started_at": "2026-07-14T09:00:00.000Z",
"completed_at": "2026-07-14T09:00:04.512Z"
}
That response is the whole tutorial in one object:
- The content is gone.
metadatawas{ "ticket_id": "ZD-4821" }in step 1 and isnullnow, along witherrorandextraction. - The erasure is provable.
content_redacted_atcarries the timestamp. A record that never held content and a record that was purged are different things, and this field is what tells them apart — which is why the call answers200with this skeleton instead of404. A404is indistinguishable from an id that never existed, so it would prove nothing. - The audit trail survives.
status,stop_reason, the timestamps and theaction_idyou labelled the spend with are all still here.
The last one is not decoration. The generation still prices:
- CLI
- SDK
- curl
naturali get-generation-usage \
--generation-id gen_V1StGXR8Z5jdHi6B
const { data: usage } = await naturali.generations.getGenerationUsage({
path: { project_id: PROJECT, generation_id: 'gen_V1StGXR8Z5jdHi6B' },
});
console.log(usage?.cost_usd, usage?.total_tokens);
curl "$NATURALI_API/projects/$PROJECT/generations/gen_V1StGXR8Z5jdHi6B/usage" \
-H "Authorization: Bearer $NATURALI_TOKEN"
{
"generation_id": "gen_V1StGXR8Z5jdHi6B",
"currency": "USD",
"cost_usd": 0.00042,
"input_tokens": 412,
"output_tokens": 87,
"total_tokens": 499
}
You erased a person's content and your invoice still reconciles. Running the
purge again is safe: it answers 200 and leaves the original
content_redacted_at in place, so a retry after a timeout cannot rewrite your
proof.
What this does not cover
- The model provider. These calls govern what this platform stores. What the provider that ran the generation does with the text it received is governed by your agreement with that provider — directly with them, for a BYOK provider.
- Erasing the conversation itself. A session transcript is a separate record from the trace that executed it.
What's next
- Stop recording it in the first place. Purging says "we deleted it";
trace_content_mode: nonesays the content was never written, which no sweep can miss and no backup can keep. Set it per project or tighten it on one agent — see Zero-retention agents. - Automate this. Rather than purging run by run, give the project a
trace_content_retention_dayswindow and a daily sweep purges anything past it, through this same path and leaving this same proof — see Content retention. - Read a run before you erase it. Traces shows what the step payload contains while it still exists.