> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# CDP PII tokenization and vault: govern sensitive identifiers

> Tokenize contact identifiers so segmentation and activation run on tokens instead of cleartext, classify traits and event properties by sensitivity and allowed use, read the data-catalog rollup, and set the erasure-propagation policy — every knob a tenant owns on Integrations → CDP.

# CDP PII tokenization and vault

Six tabs on **Integrations → CDP** hold the tenant-owned controls for handling personal data in the CDP: **PII tokenization**, **PII vault**, **Trait governance**, **Event property governance**, **Data catalog**, and **Erasure propagation**. Together they cover one workflow — decide which identifiers get tokenized, run the tokenize/detokenize operations, classify what each trait and event property may be used for, audit the result in the catalog, and carry an erasure decision into every downstream destination.

None of these surfaces mandates a compliance posture. Each one documents a control *your* organization configures: what to tokenize, who may classify, which uses you allow. The console exposes the same operations as the public API, so every section below shows both where the knob lives in the dashboard and the endpoint that drives it.

All endpoints are rooted at `https://api.orbit.devotel.io/api/v1/cdp`. Reads and tokenize calls need owner, admin, or developer with the `contacts:read` scope; policy writes need `contacts:write`; every detokenize call narrows the role to owner or admin because it returns cleartext PII.

## 1. What PII tokenization governs

Tokenization replaces a sensitive identifier — an email, a phone number, a national identifier — with an opaque token before the value travels anywhere downstream. Segments, activations, and exports then run on tokens instead of cleartext, so a breached export or a leaked destination carries pseudonyms, not identities. Orbit exposes two families of token:

* **Deterministic reversible** — the same cleartext value always maps to the same token inside your tenant (casing and whitespace variants of an email collide to one token), and a privileged caller can reverse the token back to cleartext. Use this when a downstream system occasionally needs the raw identifier back — a legal request, a fulfillment handoff.
* **Hash** — an irreversible keyed pseudonym. The value is still deterministic (the same person's records join on one token), but nothing can reverse it. Use this when joining and segmentation are all you need and you never want cleartext recoverable.

The **PII tokenization** tab renders the effective policy for each identifier type — `email`, `phone`, `ssn`, `generic` — with its override registry and a roll-up of how many types are enabled, and how many of those are reversible versus hashed. A type with no override resolves to disabled, so tokenization is opt-in per type.

### Policy API

```bash theme={null}
# Read the effective policy, overrides, type catalog, and summary
curl https://api.orbit.devotel.io/api/v1/cdp/pii-tokenization \
  -H "X-API-Key: dv_live_sk_your_key_here"

# Opt a type in — or switch its mode. The PUT upserts one type's override
curl -X PUT https://api.orbit.devotel.io/api/v1/cdp/pii-tokenization/ssn \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "enabled": true, "mode": "hash", "notes": "SSNs join on pseudonym only" }'

# Remove a type's override — reverts it to the platform default: disabled
curl -X DELETE https://api.orbit.devotel.io/api/v1/cdp/pii-tokenization/ssn \
  -H "X-API-Key: dv_live_sk_your_key_here"
```

The GET response carries four blocks: `policy` (the resolved mode per type, with `source` telling you whether it came from an `override` or the `default`), the `overrides` registry, a `catalog` describing each type's canonicalization (for example, email is lower-cased and trimmed so casing variants collide), and a `summary` with `enabled_types`, `reversible_types`, and `hashed_types` counts. A DELETE on a type with no override returns `404` — deletes are intentional, not idempotent resets.

### Tokenize and detokenize

```bash theme={null}
# Tokenize up to 100 items per call. mode is optional and defaults to the
# type's effective policy mode
curl -X POST https://api.orbit.devotel.io/api/v1/cdp/pii-tokenization/tokenize \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "items": [ { "type": "email", "value": "ada@example.com" }, { "type": "ssn", "value": "123-45-6789" } ] }'
# → { "results": [ { "type": "email", "mode": "reversible", "token": "tok:..." } ] }

# Detokenize reverses reversible tokens. Hash tokens report IRREVERSIBLE
curl -X POST https://api.orbit.devotel.io/api/v1/cdp/pii-tokenization/detokenize \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "tokens": [ "tok:v1:email:r:<token-id>" ] }'
```

Tokenize never echoes the cleartext back — items that canonicalize to empty return a per-item error code instead of failing the batch. Detokenize is the privileged read: it is owner/admin only, every call is written to your tenant's audit trail recording how many tokens were opened and which identifier types were revealed (never the values), and it is rate-limited to 60 calls per minute per tenant against 120 for tokenize. Within one tenant the tokenization keys are derived per organization, so a token from one tenant is unusable — and undecodable — in another.

## 2. PII vault — sealed envelopes and operator-open semantics

The **PII vault** tab is the sealed-envelope companion to policy-driven tokenization. Where `/pii-tokenization` applies your per-type policy, the vault is an explicit operator flow: you decide at call time whether a token must be openable later.

```bash theme={null}
# Tokenize a batch — set reversible to receive a sealed envelope per record
curl -X POST https://api.orbit.devotel.io/api/v1/cdp/pii-vault/tokenize \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "records": [
      { "type": "email", "value": "ada@example.com", "ref": "crm-row-4812" }
    ],
    "reversible": true
  }'
# → { "tokenized": [ { "index": 0, "ref": "crm-row-4812", "type": "email",
#     "token": "pii_email_...", "sealed": "enc:v1:..." } ], "skipped": [], ... }

# Detokenize opens sealed envelopes — the opaque token by itself never reverses
curl -X POST https://api.orbit.devotel.io/api/v1/cdp/pii-vault/detokenize \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "sealed": [ "enc:v1:<envelope-id>" ] }'
```

Three properties define the vault semantics:

* **The opaque token alone is never reversible.** The deterministic lookup token is a keyed pseudonym; only the `sealed` (`enc:v1:`) envelope — issued when you request `reversible: true` — can be opened back to cleartext. Store the envelope if you will need detokenize later; by default the response carries tokens only and the cleartext is unrecoverable from it.
* **Opening is operator-gated.** Detokenize narrows to owner/admin, and each call is audited with counts only — the audit entry records how many envelopes were requested and how many opened, never an identifier, token, or envelope.
* **Skipped, never partial.** A value that is not a valid identifier of its declared type (for example a malformed national id) lands in the `skipped` list with its request index and your `ref` correlation key — the batch succeeds and bad input is reported, not hashed into a meaningless token.

Use the policy surface when tokenizing should be your standing posture; use the vault when one workflow needs a reversible handoff while everything else stays irreversible.

## 3. Trait governance — classify contact traits

The **Trait governance** tab classifies each contact trait by sensitivity, data categories, consent gating, and export masking. A trait with no entry resolves to `public`, so the registry is an opt-in overlay on your trait list.

The sensitivity scale is ordered — most liberal to strictest: `public`, `internal`, `pii`, `regulatory` (GDPR special-category, HIPAA PHI, government IDs, financial accounts). Categories are a closed set of tags a data protection officer can filter by — `email`, `phone`, `name`, `address`, `geo`, `dob`, `government_id`, `financial`, `health`, `biometric`, `demographic`, `behavioral`, `device`, `ip`, `other`. Masking is a read-time projection for compliance exports (`none` through full redaction); it never rewrites the stored value.

```bash theme={null}
# Read the whole classification registry
curl https://api.orbit.devotel.io/api/v1/cdp/trait-governance \
  -H "X-API-Key: dv_live_sk_your_key_here"

# Classify one trait
curl -X PUT https://api.orbit.devotel.io/api/v1/cdp/trait-governance/lifetime_ssn_hash \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "sensitivity": "regulatory",
    "categories": ["government_id"],
    "consent_required": true,
    "masking": "hash"
  }'

# Remove a classification — the trait falls back to the public default
curl -X DELETE https://api.orbit.devotel.io/api/v1/cdp/trait-governance/lifetime_ssn_hash \
  -H "X-API-Key: dv_live_sk_your_key_here"

# Preview the effective per-role access matrix
curl "https://api.orbit.devotel.io/api/v1/cdp/trait-governance/access?role=developer" \
  -H "X-API-Key: dv_live_sk_your_key_here"
```

The `access` view answers "which traits may a given role view or export?" — call it without `role` to answer for your own role, or pass a role to preview another's access without impersonating it.

### Advisory classification by name

You do not have to hand-write every entry. The classify endpoint inspects a trait's *name* — never its values — and suggests a classification based on naming patterns:

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/cdp/trait-governance/classify \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "trait_names": ["email_hash", "churn_score", "health_risk_score"] }'
```

Each suggestion returns the proposed `sensitivity`, `categories`, `consent_required`, and `masking`, plus the naming pattern that matched — a suggestion is advisory until you PUT it into the registry. Up to 200 names per call.

## 4. Event-property governance — the data dictionary for events

Traits are the contact-side half of the dictionary. The **Event property governance** tab covers the other half: the properties carried on tracked events. Classification is keyed by `event_name → property_name` and reuses the same vocabulary as trait governance — one sensitivity scale, one category set, the same masking strategies — so a property and a trait classify on identical axes.

Each entry carries `sensitivity`, `categories`, an accountable `owner`, `allowed_use` (the governance policy — `analytics`, `personalization`, `activation`, `ml_training`, `billing`, `support`, `fraud_detection`), an optional `consent_required` gate with a specific `consent_channel`, and `masking`. Only `sensitivity` is required; a minimal `{ "sensitivity": "pii" }` is a valid write, and everything else defaults to the unconstrained posture.

```bash theme={null}
# Read every event's property registry
curl https://api.orbit.devotel.io/api/v1/cdp/event-property-governance \
  -H "X-API-Key: dv_live_sk_your_key_here"

# Read one event's properties
curl "https://api.orbit.devotel.io/api/v1/cdp/event-property-governance/Order%20Completed" \
  -H "X-API-Key: dv_live_sk_your_key_here"

# Classify one property on one event
curl -X PUT "https://api.orbit.devotel.io/api/v1/cdp/event-property-governance/Order%20Completed/card_last4" \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "sensitivity": "pii",
    "categories": ["financial"],
    "owner": "data-platform",
    "allowed_use": ["analytics", "support"],
    "consent_required": false,
    "masking": "redact"
  }'

# Remove one property's classification
curl -X DELETE "https://api.orbit.devotel.io/api/v1/cdp/event-property-governance/Order%20Completed/card_last4" \
  -H "X-API-Key: dv_live_sk_your_key_here"

# Advisory classification — same name-only heuristic as traits
curl -X POST https://api.orbit.devotel.io/api/v1/cdp/event-property-governance/classify \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "event_name": "Order Completed", "property_names": ["card_last4", "order_total"] }'
```

## 5. Data catalog and erasure propagation

The **Data catalog** tab is the read-only rollup of the classification work: one row per declared tracking-plan event plus one row per property, each carrying the sensitivity (`none`, `pii`, `phi`, `financial`, `sensitive`), owner, allowed use, and policy reference attached to it. Unclassified entries surface with `sensitivity: "none"` and `classified: false` on purpose — the governance value is partly in seeing what has not been classified yet. Filter by sensitivity, owner, or classified-only; the summary counts classified versus total entries per class.

```bash theme={null}
curl "https://api.orbit.devotel.io/api/v1/cdp/data-catalog?sensitivity=pii&classified_only=false" \
  -H "X-API-Key: dv_live_sk_your_key_here"
```

The [data catalog and identity rules guide](/guides/cdp-data-catalog-and-identity-rules) covers the catalog in depth, including how classification is authored inside the tracking plan itself.

The **Erasure propagation** tab holds the per-destination delete-versus-suppress policy that carries a completed erasure into every connected destination — HubSpot, Salesforce, Braze, Iterable, Customer.io, Klaviyo, and the CRM/ESP connectors. The policy is a map: each destination resolves to `Delete`, `Suppress`, or its native default, and the PUT fully replaces the map — a destination you omit reverts to its default.

```bash theme={null}
# Read the resolved per-destination policy
curl https://api.orbit.devotel.io/api/v1/cdp/erasure/destination-modes \
  -H "X-API-Key: dv_live_sk_your_key_here"

# Persist overrides — the PUT fully replaces the map
curl -X PUT https://api.orbit.devotel.io/api/v1/cdp/erasure/destination-modes \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "destination_modes": { "hubspot": "suppress" } }'
```

[CDP erasure propagation](/guides/cdp-erasure-propagation) is the full operator guide for that surface — fanning a DSAR deletion into every downstream store, reading the per-destination status trail, and re-running a stuck leg.

## 6. Wiring the classify heuristics without leaking raw values

Both classify endpoints are deliberately name-only. They match on *how a field is named* — `email_hash`, `health_risk_score`, `card_last4` — and never see the values the field carries. That property matters for a lawful rollout:

1. Export your trait names and event property names from the tracking plan or the data catalog — names only, no sampled values.
2. POST them to `/cdp/trait-governance/classify` or `/cdp/event-property-governance/classify` in batches of up to 200.
3. Review each suggestion — the response includes the naming pattern that matched, so a wrong match is auditable — and adjust before persisting.
4. PUT the reviewed entries into the registry.

At no point does a raw identifier, an email, or a health value leave your tenant in this flow: the heuristic input is a name, and its output is a suggestion you ratify. If a field's name does not match any pattern, the endpoint returns `suggested: null` for it — classify it by hand rather than forcing the heuristic.

## 7. End-to-end checklist

A complete posture pass touches each tab once:

1. **PII tokenization** — enable the types you segment on (`reversible` where a downstream handoff needs cleartext, `hash` where it never should), then verify with GET that the summary counts match your intent.
2. **PII vault** — run one reversible tokenize for the handoff workflow, store the sealed envelope, and confirm detokenize opens it under an owner/admin key.
3. **Trait governance** — classify every personal trait; run `/classify` first to draft, review, then PUT.
4. **Event property governance** — classify the properties on your tracking plan events the same way, with `allowed_use` set where a purpose must be constrained.
5. **Data catalog** — filter `classified_only=false` and drive the unclassified remainder to zero.
6. **Erasure propagation** — confirm every connected destination resolves to the delete-or-suppress mode your counsel's posture requires.

<Info>
  Every control on this page is tenant-owned configuration. Orbit documents
  the knobs; the posture is yours to set with your counsel. Nothing here
  mandates a specific compliance stance, and the defaults — tokenization
  disabled, traits at `public`, destinations at native — leave existing
  tenants exactly as they were.
</Info>

## Triage

| Symptom                                                       | Likely cause                                                                      | Fix                                                                                                                                     |
| ------------------------------------------------------------- | --------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `404` on DELETE `/cdp/pii-tokenization/:type`                 | The type has no stored override                                                   | Nothing to remove — the type is already at its platform default (disabled).                                                             |
| Detokenize returns `IRREVERSIBLE` for an item                 | The token was minted in `hash` mode                                               | Hash tokens are one-way by design. Re-mint in `reversible` mode if the workflow genuinely needs cleartext.                              |
| Detokenize returns `MALFORMED_TOKEN`                          | The token came from another tenant or another surface                             | Tokens are tenant-scoped; detokenize only resolves tokens minted under your organization.                                               |
| Vault `tokenize` skips a record with `unnormalizable`         | The value is not a valid identifier of its declared type                          | Fix the value or its declared type; skipped records are counted and never hashed into a meaningless token.                              |
| `422` with `VALIDATION_ERROR` on vault calls                  | Body failed the request schema (over 100 records, missing `type`/`value`)         | Batch to at most 100 records per call and include `type` + `value` on every record.                                                     |
| Classify returns `suggested: null`                            | The name matched no heuristic pattern                                             | Classify by hand — the suggestion endpoint is advisory and name-only.                                                                   |
| Data catalog shows `classified: false` rows after classifying | Classification was set via trait/event-property registries, not the tracking plan | The catalog rolls up tracking-plan classification; the registry tabs govern traits and event properties separately — keep both current. |
