Skip to main content
A dataset’s sample is up to 1,000 rows that Narrative copies from your data plane into the control plane so you can preview it. When those rows contain personal data, you can store a redaction for the sample: a record of which columns hold personal data, what kind of personal data each column holds, and how each value is masked when the sample is read. The redaction only affects GET /datasets/{dataset_id}/sample/redacted. The underlying sample is not modified — deleting the redaction leaves the sample untouched, and GET /datasets/{dataset_id}/sample continues to return unmasked rows.
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-sample first if one does not exist.
  • An API key with write access on datasets.

The four endpoints

Upload a redaction

Send PUT /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.
A successful 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 with type. keep_last_digits requires a count between 1 and 4:
A value the strategy can’t parse (an email with no @, 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_number
  • other
  • private_address
  • private_date
  • private_email
  • private_person
  • private_phone
  • private_url
  • secret
category is optional. Send null or leave the key out for a column you don’t want to classify.

Read the redacted sample

GET /datasets/{dataset_id}/sample/redacted returns the sample with every named column masked and every other column untouched.
Masking a number or a boolean returns the text it renders as, so a masked numeric column comes back as a JSON string. Redacted rows do not match the dataset’s declared schema for those columns.
The endpoint returns 400 when no sample or no redaction is stored for the dataset. 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.

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:
Both read endpoints return 500 if the stored redaction cannot be decoded — for example, a stored masking_strategy 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.

Sample Data

How samples move from data planes to the control plane

Hashing PII for Upload

Prepare identifiers before uploading them