Skip to main content

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:

  1. Find the run to erase.
  2. Purge its content.
  3. Prove the content is gone.

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

  1. A credential. A nat_sk_… API key (or a session JWT from Auth) exported as NATURALI_TOKEN, and your client set up — the CLI, the SDK or plain curl against https://api.naturali.ai/v1:

    export NATURALI_TOKEN=nat_sk_...
    export NATURALI_API=https://api.naturali.ai/v1 # curl examples only
  2. 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_V1StGXR8Z5jdHi6B
    export AGENT=agent_V1StGXR8Z5jdHi6B
    export 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.

naturali list-agent-generations \
--agent-id agent_V1StGXR8Z5jdHi6B \
--limit 5
{
"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.

naturali purge-trace-content \
--trace-id trace_V1StGXR8Z5jdHi6B
{
"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.

Purge the trace, not just the generation

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.

naturali get-generation \
--generation-id gen_V1StGXR8Z5jdHi6B
{
"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. metadata was { "ticket_id": "ZD-4821" } in step 1 and is null now, along with error and extraction.
  • The erasure is provable. content_redacted_at carries 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 answers 200 with this skeleton instead of 404. A 404 is indistinguishable from an id that never existed, so it would prove nothing.
  • The audit trail survives. status, stop_reason, the timestamps and the action_id you labelled the spend with are all still here.

The last one is not decoration. The generation still prices:

naturali get-generation-usage \
--generation-id gen_V1StGXR8Z5jdHi6B
{
"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: none says 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_days window 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.