Export a person's data
Get one archive of what is held about one person, including recorded agent session replays and evaluations of their own whole runs, with a manifest that says exactly what is in it, how fresh it is and what is not in it.
On this page
Exporting a person answers a request for access: someone asks what you hold about them, and you hand them a file. It changes nothing about the person. Only an owner or administrator can do it, and who asked is recorded.
It is the counterpart of erasing a person, and it reaches the same person the same way: you name them by their person id or by any one of their identifiers, and the export covers every identifier linked to them.
You start an export in the Console: open Privacy (/privacy), stay on the Requests tab, and
find the person first. See Request an export and the
Console guide. Your agent can request and read the same export with
the MCP tools or the CLI commands in that section.
What you get
One zip archive. Inside it:
- one file per kind of record, named
<kind>.ndjson. Each line is one JSON object, so the file can be read a line at a time by any tool; manifest.json, which lists every kind with its number of records, the time range they cover and the moment the kind is complete as of, says how recent the archive's data is, and lists what the archive does not contain and why.
Every kind has a file, including the ones with no records: an empty file says "there were none", which a missing file could not.
| File | What it holds |
|---|---|
person.ndjson |
The person's profile: when they were first seen, whether they are identified, and their current properties (everything your identify() calls attached, such as email, name and plan). |
identifiers.ndjson |
Every identifier linked to the person, including the anonymous ones from before they signed in, with when each was linked. |
identity_history.ndjson |
When each identifier was linked, moved or merged. |
person_property_history.ndjson |
Every recorded change to the person's properties. |
product_events.ndjson |
The product events captured under any of the person's identifiers, such as page views and the custom events you send, with their properties. |
errors.ndjson |
The error occurrences recorded under any of the person's identifiers. |
diagnostic_captures.ndjson |
Diagnostic sampling requests that name the person: their limits, status and times. Captured telemetry is in the error, log and span files. |
diagnostic_grants.ndjson |
Retained sampling grants issued under the person's identifiers, with their budgets, acknowledgement and expiry. |
fix_subjects.ndjson |
Fix declarations that named this person, with their own affected operations and declaration metadata. Other people, shared history and the operator note are excluded. |
problem_reports.ndjson |
The person's submitted problem reports, including their text and route when the project's export content policy permits them. |
logs.ndjson |
The log lines recorded under any of the person's identifiers. |
spans.ndjson |
The trace spans recorded under any of the person's identifiers, with their events. |
agent_events.ndjson |
The agent events recorded under any of the person's identifiers. |
agent_session_replays.ndjson |
Recorded replays of the person's agent sessions: replay identity, outcome, progress and times, with ordered turns, comparisons, the person's words and agent replies when your export content policy permits them. Exact reply bytes are base64 in reply_json_base64. |
cohort_memberships.ndjson |
The cohorts the person is in, including the earlier versions of a cohort's member list that are kept as history. |
experiment_assignments.ndjson |
The experiments the person was assigned to, and which arm. |
session_recordings.ndjson |
The session recordings made under any of the person's identifiers: when each started and ended and how large it is. |
survey_receipts.ndjson |
Which surveys the person was shown, answered or dismissed, the revision and when; caller-chosen display, response or dismissal identifiers when the project's export content policy permits them. Their answers are in product_events.ndjson. |
survey_offers.ndjson |
Which surveys the person was offered, the revision and when. |
evaluation_jobs.ndjson |
Evaluation attempts about the person's own runs, their status, verdicts and execution details; references to the evaluator and experiment, without their shared definitions. |
evaluation_dataset_items.ndjson |
Cases frozen from the person's own runs, with their promotion and review record and content your export policy permits. |
evaluation_trials.ndjson |
Experiment trials about the person's own runs or their frozen cases; references to candidates, without shared configurations. |
evaluation_annotations.ndjson |
Reviewers' judgments about the person's own runs and frozen cases; comments your export policy permits. |
evaluation_results.ndjson |
Quality scores about the person's own runs and frozen cases, with evaluation provenance and explanations your export policy permits. |
group_memberships.ndjson |
The accounts (groups) the person's identifiers are members of, including memberships that have ended: the account's type and key, whether the membership is current, and when it began and last changed. |
group_membership_history.ndjson |
When each of those account memberships began, ended or moved to another account. |
A run or trace belongs wholly to this person when a retained, visible span or agent event names one of their current identifiers, or it belongs to their own session or turn, and every such record that names somebody names one of those identifiers. Their whole runs include records that name nobody, such as child spans; a session or turn with a trace naming someone else is shared. When someone else's only identifying row was already erased, that hidden row no longer makes the remaining run shared. Shared runs contribute only records carrying this person's identifiers.
How fresh an archive is
Data you send does not become readable the instant it is accepted: it takes a moment to be stored, usually a few seconds and up to about a minute when little is being sent. An export that read the person's data the moment you asked could miss what they did just before. So an export waits before it reads, and then tells you exactly how fresh it is.
The wait. Every export waits 60 seconds after it is requested. That is longer than data that
was already being written at that moment normally takes to be stored. It is not a guarantee: a
write that fails is retried and takes longer, and data that had been accepted but was still
queued, not yet being written, is not covered by the 60 seconds at all. After that the export
checks whether the product events and identity updates your project had sent before the request
have been stored. If some have not, or the check cannot be made, it tries again every few seconds
and reads as soon as they have. It stops waiting three minutes after the request whatever it
finds, and the archive is then built as soon as the platform gets to it: normally within seconds,
later when several exports are being built at once. While it waits, the export's state is
PERSON_EXPORT_STATE_WAITING_FOR_RECENT_DATA and waiting_until says when it will next be looked
at. Nothing is wrong and there is nothing to do.
"Complete as of". Every kind of record that was read carries complete_as_of, on the export
and in the manifest. It means: this file contains every record of this kind that was stored at
that moment under the identifiers and whole runs the archive was built from. A record that was stored later may
be missing. Telemetry and evaluations of whole runs share one moment, taken before deciding which runs
belong wholly to the person. Neither that decision nor those files include records accepted
after it. A record accepted earlier can finish processing during the build: the set of whole runs
stays fixed, but the archive is not a frozen copy of storage at that moment. Other kinds take their
own moment just before reading. Erasure and retention still
remove records, and mutable evaluation status and review details are their current values; this
is not a historical snapshot. The earliest
complete_as_of across the kinds is the moment the whole archive is complete as of; the Console
shows that one.
Two limits come with it, and the export states both:
- The identifiers are read once. An export first reads which identifiers are the person's, and
records and whole runs in the archive are then found by those identifiers.
identifiers_read_at, on the export and in the manifest, is when that was. An identifier that became the person's after it, for example an anonymous visitor who signed in while the archive was being built, is not covered, and neither are the records stored under it. Request a new export to include them. - A kind with nothing to read has no moment. A kind is marked
nothing_to_readwhen the person has no identifier that kind of record is stored under, so there was nothing to ask for. Its file is empty and it has nocomplete_as_of. That is different from an empty file with acomplete_as_of, which says the records were looked for at that moment and there were none.
What was still on its way. freshness on the export, and the same section of the manifest,
says how long the export waited and what it found. What it found is always about one period:
the product events and identity updates your project sent between capture_accepted_from and
capture_accepted_through. capture_accepted_through is the request. capture_accepted_from is
how far back the check reached: up to twenty-four hours before the request, and at least one.
Nothing sent before capture_accepted_from was checked, and no state says anything about it.
capture_state |
Meaning |
|---|---|
PERSON_EXPORT_CAPTURE_STATE_SETTLED |
Of what your project sent in that period, nothing was still waiting to be stored at capture_checked_at, before the archive was read. |
PERSON_EXPORT_CAPTURE_STATE_PENDING |
The three minutes ran out with some still waiting to be stored. capture_pending says how many, and capture_oldest_pending_accepted_at when the oldest of them was sent. If the person was active in that time, request a new export. |
PERSON_EXPORT_CAPTURE_STATE_UNKNOWN |
The check could not be made before the three minutes ran out. capture_unknown_reason says why: record_unavailable (it could not be read) or check_incomplete (it answered, but not for a period that can be stated). The export waited and claims nothing more than each kind's complete_as_of. |
Every one of these counts is of your whole project, not of this person: the check cannot tell whose an event is.
capture_not_stored is a separate count for the same period: events that were not stored and
that the export did not wait for, because waiting could not store them. An event is counted here
when it was set aside as unprocessable after it was accepted, or when the platform never
confirmed handing it on for storage (the request that sent it was told so, or was cut off). If
any of them were this person's, they are not in the archive. It is normally 0.
That check covers product events and identity updates. For errors, logs, trace spans and agent events there is no such check, and the 60-second wait is all there is: one that was accepted before the request and took longer than that to be stored is not in the archive. If your telemetry was arriving late when you asked, request the export again later.
Nothing sent after the request is promised. An export is a record as of a moment, and it says which.
What it does not contain
Read this before you tell someone the archive is everything. The archive says the same in its manifest, so the file you hand over is honest by itself.
- Identity-less records and evaluations of shared runs. A trace or agent session that names someone else contributes only records carrying this person's identifiers. Records that name nobody in a shared run, and evaluations of that shared activity, describe others too and are left out. Records that name nobody in this person's whole runs are included.
- Shared evaluation definitions and delivery copies. Evaluator, dataset, experiment and candidate configurations describe the customer's setup across many runs. The archive holds the references and the attempts, cases, trials, judgments and results about this person's runs; delivery copies of those results add no personal fact.
- The content of session recordings. The archive lists each recording. The recording itself is not in the archive; a replay link from your agent opens it on a read-only proof page.
- Content your project's content policy keeps out of exports. If your
content policy does not allow a class of recorded content to be
exported, it is left out of this export too, and the manifest names it. A project that has not
changed its policy exports model prompts, completions and tool call arguments, and does not
export the person's property bag:
person.ndjsonthen carries the profile without its properties and says they were withheld, andperson_property_history.ndjsonis empty. Allow person properties at the export boundary in your content policy, then request a new export, to include them. - The properties of the accounts the person belongs to. The archive lists the person's account memberships. An account's properties describe the account and every member of it, not the person.
- The person's rows inside analytics results that were already measured. A measured result
keeps working copies of what it counted until the result expires. Each copies a product event,
with the number or value the analysis read from it; the events themselves are in
product_events.ndjson. Where an analysis grouped people by a profile property, the copy also holds the value that property had when the analysis ran, for the person or for one of their accounts. The person's properties and their recorded changes are inperson.ndjsonandperson_property_history.ndjsonwhen your content policy allows person properties to be exported; an account's properties are not exported (see above). - Records that were refused when they were sent. A refused record is kept as raw bytes for seven days so the refusal can be diagnosed. It was never accepted as a record about anyone, and it cannot be picked out by person without the rest of the batch it was sent in.
- Not yet part of the export: quality scores, review notes and evaluation dataset items about the person's agent runs; and action receipts that name them.
- The audit log and the records of earlier deletions made for the person. They record what your organization's operators did, not data collected from the person.
Who can export
The persons:export permission, which owners and administrators hold. An API key can be given it
explicitly; a project-scoped key is confined to its own project.
persons:export is not enough by itself. The credential must also hold every permission that
reads the person's data: persons:read, persons:profile:read, analytics:read, errors:read,
logs:read, traces:read, agents:read, agents:content:read, experiments:read, groups:read,
replay:read, evals:read, scores:read and surveys:read. A credential missing one is refused with a message that names it. That is
deliberate: an archive built for someone who could not read the logs would be an archive without
logs, and nothing about the file would say so.
Survey receipt identifiers require surveys:read and agents:content:read, and
the project's export policy must permit both model_transcript and
tool_arguments, just as for survey answers. A denied or unreadable policy
withholds the identifiers and names them with the usual content-withheld marker.
The response or dismissal outcome and its time remain visible. Survey offers
have no caller-chosen display, response or dismissal identifier.
Recorded replay text requires your export policy to permit model_transcript.
Opaque reply bytes and caller-chosen session, turn, run and requester identifiers
also require tool_arguments, because they can carry other content. A denied or
unreadable policy withholds those values and names the fields in the row and in
the manifest; replay outcome, counts and times remain visible. Response digests
and internal deletion or delivery bookkeeping are left out because they add no
recorded words or outcome. Replays that resolved no person are not included.
The same permissions are checked again every time the archive is read or downloaded. Removing one from a credential stops it downloading archives that were already made.
Request an export
An export is of one person in one project. It is built in the background: the request returns at once with the export pending, and the export becomes complete when the archive is stored. That takes a little over a minute, because the export first waits for recently sent data, and longer when there is a lot to read, data is still being stored, or other exports are being built.
In the Console. Open Privacy (/privacy) and stay on the Requests tab. In the A person
card, enter the person's id or any one of their identifiers and choose Find person. The page
looks the person up first, and you can start an export only for a person it finds. Choose Export
this person's data. The dialog shows the export waiting for recent data, then being built, and then
what the archive holds, the moment it is complete as of, what it does not hold, and a Download
archive button.
From the command line.
anectico persons export user@example.com
anectico persons exports get <export-id>
anectico persons exports download <export-id> --file person.zip
persons export prints an export id before it sends the request. If the request is interrupted,
pass that id back with --export-id to retry the same request rather than start a second one.
From an agent. request_person_export previews whose data the export covers and asks for
confirmation; get_person_export reads it; get_person_export_download returns where the archive
is downloaded. The export has no proof page. The CLI also lists exports with
anectico persons exports list and prints the download address with
anectico persons exports download-url <export-id>.
From the API.
POST /api/v1/projects/{projectId}/person-exports
{
"person": "user@example.com",
"export_id": "6f3c7c1e-1c0b-4a55-9d5e-1d1f0b1c2a3b"
}
person is the person's id or any one of their identifiers. It is matched exactly as you send it,
including case and surrounding spaces. It goes in the body, never in the URL.
export_id is a UUID you generate. Sending the same id again returns the export that was already
recorded, with "already_recorded": true, so a retry never builds a second archive. The same id
for a different person is refused.
The response is the export:
{
"export": {
"export_id": "6f3c7c1e-1c0b-4a55-9d5e-1d1f0b1c2a3b",
"project_id": "…",
"person_id": "…",
"requested_by": "user:…",
"requested_at": "2026-10-05T12:00:00Z",
"state": "PERSON_EXPORT_STATE_PENDING",
"kinds": [],
"not_included": []
},
"already_recorded": false
}
No response contains one of the person's identifiers. person_id is the person's id, and
distinct_id_count says how many identifiers the archive was built from.
Read an export
GET /api/v1/projects/{projectId}/person-exports/{exportId}
GET /api/v1/projects/{projectId}/person-exports?person_id=…&limit=50&cursor=…
state is one of:
| State | Meaning |
|---|---|
PERSON_EXPORT_STATE_PENDING |
Recorded, not looked at yet (or about to be tried again after part of the data could not be read). |
PERSON_EXPORT_STATE_WAITING_FOR_RECENT_DATA |
Deliberately not built yet: it is waiting for recently sent data to be stored. waiting_until says when it is looked at again. |
PERSON_EXPORT_STATE_RUNNING |
Being built. |
PERSON_EXPORT_STATE_COMPLETE |
The archive is stored and can be downloaded until expires_at. |
PERSON_EXPORT_STATE_FAILED |
No archive was stored. error_code and error say why. |
PERSON_EXPORT_STATE_EXPIRED |
The archive was deleted when its retention period ended. |
Once complete, kinds lists every file with its rows (a decimal string), its earliest and
latest record times and its complete_as_of, or nothing_to_read; identifiers_read_at is
when the person's identifiers were read; freshness says how long the export waited and what was
still being stored, for which period (waited_seconds, settle_window_seconds, capture_pending
and capture_not_stored are decimal strings); not_included repeats the manifest's list; and
size_bytes is the archive's size.
{
"state": "PERSON_EXPORT_STATE_COMPLETE",
"kinds": [
{
"kind": "product_events",
"rows": "412",
"earliest": "2026-09-01T08:00:00Z",
"latest": "2026-10-05T11:59:48Z",
"complete_as_of": "2026-10-05T12:01:01.204Z",
"nothing_to_read": false
}
],
"identifiers_read_at": "2026-10-05T12:01:01.190Z",
"freshness": {
"reading_began_at": "2026-10-05T12:01:01Z",
"waited_seconds": "61",
"settle_window_seconds": "60",
"capture_state": "PERSON_EXPORT_CAPTURE_STATE_SETTLED",
"capture_checked_at": "2026-10-05T12:01:01.180Z",
"capture_pending": "0",
"capture_accepted_from": "2026-10-04T12:00:00Z",
"capture_accepted_through": "2026-10-05T12:00:00Z",
"capture_oldest_pending_accepted_at": null,
"capture_not_stored": "0",
"capture_unknown_reason": ""
}
}
There is no limit on the number of whole runs selected for an archive. The archive's size, one record's size and the time available to build it still apply. A build attempt has at most two hours; if that expires, the export fails without a partial archive. Temporary working copies of run identifiers are removed after the build, or after an abandoned build's expiry, and are removed when the person or project is deleted.
A failed export stored nothing. It never stores part of a person:
error_code |
What happened | What to do |
|---|---|---|
person_not_found |
The person was erased, or otherwise no longer exists, before the archive was built. | Nothing: there is nobody to export. |
too_large |
The person's data is larger than one archive may hold. Nothing was truncated. | Contact support. |
source_unavailable |
Part of the person's data could not be read after several attempts. | Request the export again. |
project_deleted |
The project was deleted before the archive was built. | Nothing: the project's data is being deleted. |
export_failed |
Anything else. | Request the export again, or contact support. |
Download the archive
GET /api/v1/projects/{projectId}/person-exports/{exportId}/download
returns {"url": "…", "expires_at": "…"}. The URL is an address of this API. Fetch it with the
same credential:
curl -H "Authorization: Bearer $TOKEN" -o person.zip "$URL"
The URL is not a secret and grants nothing by itself: every download is authorized again with the
credential that makes it, and a long download is checked again while it is in progress.
expires_at is when the archive is deleted, not when the URL stops working.
How long an archive is kept
An archive is the person's whole record in one file, so it is not kept longer than it is needed:
- it is deleted automatically seven days after it was built. The export then reads
PERSON_EXPORT_STATE_EXPIRED; request a new one if you still need it; - it is deleted at once when the person is erased. The erasure does not finish until every archive of that person is gone, and an export requested for an erased person is refused;
- it is deleted with its project or organization.
A copy you have downloaded is yours to look after. Nothing here can delete it.
If the person is erased while an export is being built
The export stores nothing. An export that had already finished is deleted with its archive. Either way, an erasure never leaves an archive behind.