GET /datasets/{dataset_id}/sample returns the masked rows by default; pass redacted=false to get the unmasked sample instead. GET /datasets/{dataset_id}/sample/redacted always returns the masked rows and requires a stored redaction. The underlying sample itself is not modified — deleting the redaction leaves the sample untouched, and GET /datasets/{dataset_id}/sample then returns unmasked rows either way.
Writing or deleting a redaction requires an API key with write access on datasets. See API key permissions.
Prerequisites
- A dataset with a stored sample. Call
POST /datasets/{dataset_id}/request-samplefirst if one does not exist. - An API key with write access on datasets.
The endpoints
Upload a redaction
SendPUT /datasets/{dataset_id}/redaction with one entry per column that holds personal data. Each entry names the column’s dot-separated path in the sample, an optional category, and a masking_strategy.
PUT returns 200 and replaces whatever was stored before. Uploading also brings back a redaction that was previously deleted. The stored redaction records who wrote it from your access token, so a written_by field in the request body is ignored.
A single redaction may name at most 20,480 columns. A path that does not resolve in a row leaves that row untouched, so a redaction naming a since-renamed column quietly stops masking it.
Masking strategies
Seven strategies are available. Each is a JSON object tagged withtype.
keep_last_digits requires a count between 1 and 4:
@, a non-IPv4 address, a non-coordinate, a value with no four-digit run) is masked completely.
Personal data categories
category classifies what kind of personal data the column holds. It does not affect masking — only masking_strategy does. Nine values are accepted:
account_numberotherprivate_addressprivate_dateprivate_emailprivate_personprivate_phoneprivate_urlsecret
category is optional. Send null or leave the key out for a column you don’t want to classify.
Read the redacted sample
Two endpoints return the masked rows.GET /datasets/{dataset_id}/sample applies the stored redaction by default, masking every named column and leaving every other column untouched. Pass redacted=false to get the unmasked sample; a dataset with no stored redaction returns the unmasked sample either way.
GET /datasets/{dataset_id}/sample/redacted always returns the masked sample and requires both a stored sample and a stored redaction.
GET /sample/redacted returns 400 when no sample or no redaction is stored for the dataset. GET /sample returns 400 when no sample is stored, whatever the redacted value. Use POST /v2/datasets/{dataset_id}/request-sample to sample the dataset and have Narrative’s privacy model write a redaction in a single call.
View the sample in the app
Wherever a dataset’s sample is shown in the app — its own sample section, the mapping editor, and query results — the rows arrive masked by the stored redaction, the same defaultGET /datasets/{dataset_id}/sample applies. Turn on Show unredacted values below the table to reload the sample unmasked. The switch starts off each time the sample is opened, and a dataset with no stored redaction shows the same rows either way.
Update sample in a dataset’s Actions menu, and Request new sample on an empty sample card, both take the POST /v2/datasets/{dataset_id}/request-sample path: they sample the dataset and have the privacy model write the redaction for that sample in one run.
Delete a redaction
DELETE /datasets/{dataset_id}/redaction clears the stored redaction. GET /datasets/{dataset_id}/redaction then reports it as missing, and GET /datasets/{dataset_id}/sample/redacted stops returning rows until a new redaction is stored. Deleting when there is no redaction succeeds and does nothing. The underlying sample is not affected.
Errors
PUT /datasets/{dataset_id}/redaction returns 400 when a request body is malformed. error_description points at the offending column by its position in columns:
GET /redaction, GET /sample with its default, and GET /sample/redacted) returns 500 if the stored redaction cannot be decoded. This happens, for example, when the stored masking_strategy is one this API no longer recognises. The body is an RFC 7807 problem document with title: "Stored Redaction Unreadable". Quote the log_id when reporting the failure to support.
Related content
Sample Data
How samples move from data planes to the control plane
Hashing PII for Upload
Prepare identifiers before uploading them

