> ## 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.

# UID2 activation: mint, rotate, and export universal identifiers

> Activate first-party segments on the cookieless open web with Unified ID 2.0: configure the operator and destination, mint hashed identifiers, plan token and salt-bucket rotation, and export raw UID2s to The Trade Desk.

# UID2 activation: mint, rotate, and export universal identifiers

Unified ID 2.0 (UID2) turns a first-party email or phone into a portable advertising identifier the open programmatic ecosystem — The Trade Desk and the rest of the open-web bidstream — can match without third-party cookies. This guide walks the full loop: configure the operator and destination connection, mint hashed identifiers from a segment, plan for token and salt-bucket rotation, export the minted identifiers, and read back the run history. Endpoint schemas live in the [CDP API reference](/api-reference/endpoints/cdp); this page covers the workflow and the privacy posture.

## 1. What UID2 is and when to use it

The CDP's standard activation paths sync audiences to a single destination's own user graph — a walled-garden ad network audience ([Meta ads activation](/guides/ads-activation)), a CRM, or a warehouse via [reverse ETL](/guides/cdp-reverse-etl-and-warehouse-exports). That works while the destination can match your hashed PII against its own graph.

The open web has no single graph to match against. UID2 solves that with a deterministic, open-standard identifier: an email or phone is normalized, hashed, and salted into a raw UID2 that any participant in the UID2 ecosystem resolves to the same value. A segment minted once activates everywhere the open-web bidstream runs.

Use UID2 when the destination is the open web (The Trade Desk). Use the deterministic walled-garden or CRM/warehouse activation paths when the destination is a specific platform — do not mint universal identifiers for a destination that already matches hashed PII on its own graph.

## 2. Under the hood: hashed DII and the salt-bucket model

Two properties keep the mint → export loop safe to run on first-party data:

**Hashed DII only.** Directly identifying information (DII) — the email or phone — is normalized to a canonical form, then SHA-256-hashed and base64-encoded. That digest is the only value that ever leaves your workspace toward the UID2 operator. Cleartext DII is never persisted, logged, or returned anywhere in this surface.

**Salt buckets.** The operator assigns every hashed identity to one of roughly a million rotating salt buckets, and the raw UID2 is a re-hash of the DII digest together with the bucket's salt. Given the bucket salt, the derivation is deterministic — two systems holding the same salt mint identical UID2s without exchanging cleartext. Because buckets rotate on the operator's schedule (roughly yearly per identity), every raw UID2 minted from a rotated bucket must be re-minted; the rotation helpers below exist to plan that.

## 3. Step 1 — configure the operator and destination

The UID2 surface keeps its config on your organization settings; no secrets are stored — a connection id is an identifier that names which connected account to route through, not a key. Reads and writes require an owner, admin, or developer role.

Read the current config:

```bash theme={null}
curl https://api.orbit.devotel.io/api/v1/cdp/uid2/config \
  -H "X-API-Key: dv_live_sk_your_key_here"
```

The response carries `enabled` (the feature toggle), `configured` (true once any connection or audience id is set), the two connection ids, the destination `audience_id`, and the effective `salt_bucket_count`.

Wire the config — only the fields you send are touched:

```bash theme={null}
curl -X PATCH https://api.orbit.devotel.io/api/v1/cdp/uid2/config \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "enabled": true,
    "operator_connection_id": "conn_uid2_operator_9f3e",
    "export_connection_id": "conn_ttd_4b2c",
    "audience_id": "ttd_first_party_segment_118"
  }'
```

* `operator_connection_id` — the connected account to the UID2 operator (salt and token minting).
* `export_connection_id` — the connected account to the open-web destination (The Trade Desk).
* `audience_id` — the first-party data segment on the destination to export into.
* `salt_bucket_count` — optional bucket cardinality for a private operator; defaults to \~1,000,000 to mirror the production operator's bucketing scale.

Create both connections on **Settings → Connected apps** first ([connected apps guide](/guides/connected-apps)), then reference their ids here.

## 4. Step 2 — mint raw UID2s from a segment

Minting takes a batch of segment members in one call, up to 10,000 members per request:

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/cdp/uid2/mint \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "segment_id": "seg_open_web_prospects",
    "members": [
      { "email": "Jane.Doe+promo@gmail.com" },
      { "phone": "+1 555 123 4567" }
    ]
  }'
```

Each member is normalized, hashed, assigned a salt bucket, and — when you supply the bucket's salt in the optional `salts` map — resolved to its raw UID2 inline. Email normalization is Gmail-aware: the lowercased local part of a `gmail.com` address loses its dots and everything from the first `+` onward, so `Jane.Doe+promo@gmail.com` and `janedoe@gmail.com` resolve to one identity. Phones must normalize to strict E.164 form (a leading `+` and 8–15 digits). Email is the preferred key; phone is the fallback when the email is unusable.

Because minting is deterministic, members that normalize to the same identity are flagged as duplicates and skip exactly once:

```json theme={null}
{
  "data": {
    "segment_id": "seg_open_web_prospects",
    "minted_count": 2,
    "resolved_count": 1,
    "skipped_count": 0,
    "identities": [
      {
        "index": 0,
        "dii_type": "email",
        "hashed_dii": "ux0c2hdBgvqlpGsxewCTCgKZ0hIigkzFvNapvgkbTLo=",
        "salt_bucket_id": "bucket_417322",
        "raw_uid2": "KGML...base64...=="
      }
    ],
    "rejections": []
  }
}
```

`raw_uid2` comes back `null` for any member whose bucket had no salt supplied — the hashed DII and bucket id still return, so you can fetch the salt from the operator and finish the resolution. Rejections carry the member's position and a reason (`invalid_email`, `invalid_phone`, `no_dii`, `duplicate`) — never the DII itself. A mint with the feature disabled returns `409 UID2_NOT_ENABLED`.

## 5. Step 3 — plan rotation on both axes

UID2s decay on two schedules, and Orbit classifies both so you know what to re-mint or refresh:

**Token side — `/cdp/uid2/rotate/tokens`.** The bidstream carries short-lived advertising tokens wrapping the raw UID2. Each token is classified against the operator-issued timestamps:

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/cdp/uid2/rotate/tokens \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "tokens": [
      { "ref": "profile_0192", "identity_expires": 1790000000000,
        "refresh_from": 1790000000000, "refresh_expires": 1790300000000 }
    ]
  }'
```

The response buckets your `ref` ids into `valid_refs` (usable as-is), `refresh_refs` (past `refresh_from` — call the operator's refresh endpoint to roll them forward), and `expired_refs` (past `refresh_expires` — the refresh token is dead; re-establish the identity from DII). Each classification also reports whether the advertising token is still usable in the bidstream now.

**Salt side — `/cdp/uid2/rotate/buckets`.** Given the identifiers you minted (with their bucket ids and mint timestamps) and the operator's rotated-bucket list, determine which raw UID2s are stale:

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/cdp/uid2/rotate/buckets \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "identities": [
      { "ref": "profile_0192", "salt_bucket_id": "bucket_417322", "minted_at": 1789900000000 }
    ],
    "rotated_buckets": [
      { "salt_bucket_id": "bucket_417322", "last_updated": 1790000000000 }
    ]
  }'
```

A raw UID2 is stale when its bucket rotated at or after the identity was minted — the salt it was derived from no longer exists, so it will not match the operator's current derivation. Stale refs land in `remint_refs`; current ones in `current_refs`. Re-mint stale refs through `/cdp/uid2/mint` with the bucket's new salt. Both rotation endpoints are pure classifiers: they touch no data and dispatch nothing, so run them as often as your operator poll allows.

## 6. Step 4 — export to The Trade Desk

`/cdp/uid2/export` ships the minted raw UID2s — never the DII — into the destination audience:

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/cdp/uid2/export \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "segment_id": "seg_open_web_prospects",
    "operation": "add",
    "uid2s": ["KGML...base64...=="]
  }'
```

`operation` is `add` (activate) or `remove` (suppress). Blank and duplicate ids are dropped and counted in `dropped_count`; the envelope carries only the deduped raw UID2s. The export requires the feature enabled and an `audience_id` set — otherwise `409 UID2_NOT_ENABLED` or `409 UID2_AUDIENCE_NOT_CONFIGURED`.

Dispatch is graceful: with no export connection wired, or an empty id list, the run is still logged and the response reports `dispatch_skipped: true` instead of failing. A dispatched run returns `status: "ok"`; a failed dispatch attempt returns `status: "failed"` with the error message. Export is the same pattern used across the audience surfaces — the destination is reached through your connected account, and nothing leaves the workspace beyond the salted identifiers.

## 7. Step 5 — read the run history

Every mint and export records a counts-only run; the newest 50 per organization are retained:

```bash theme={null}
curl https://api.orbit.devotel.io/api/v1/cdp/uid2/runs \
  -H "X-API-Key: dv_live_sk_your_key_here"
```

Each entry carries the run kind (`mint` or `export`), the segment id, the request timestamp, and per-kind counts — `requested` / `minted` / `resolved` / `skipped` for mints, and `operation` / `member_count` / `dispatched` / `dispatch_skipped` / `status` for exports. Runs record counts only, never identifiers, so the history is safe to retain alongside the identifiers it describes.

## 8. Privacy and consent posture

UID2 export moves advertising identifiers, yet the first-party data they derive from lives under the same tenant-owned controls as every other activation surface:

* **Consent is yours to check.** Verify the segment's consent basis before minting — the [audience consent inspector](/guides/audience-consent-inspector) shows per-member consent state, and suppression lists apply to activation audiences the same way they apply to campaigns.
* **No cleartext leaves the tenant.** Minting returns only hashed DII and salted raw UID2s, exports dispatch only those digests, and the run log and audit trail record counts only.
* **Erasure cascades.** A contact deletion propagates to derived audiences on the standard erasure path ([CDP erasure propagation](/guides/cdp-erasure-propagation)); for already-exported UID2s, export with `operation: "remove"` to suppress them at the destination.
* **Access is role-gated.** UID2 reads and writes accept owner, admin, or developer roles only — the same gate as the sibling CDP controllers — and every config change and run lands in the [audit log](/guides/audit-log).

## 9. Troubleshooting

* **`409 UID2_NOT_ENABLED` on mint or export.** Set `enabled: true` on the config first; the toggle gates both writes.
* **`409 UID2_AUDIENCE_NOT_CONFIGURED` on export.** Export needs the destination `audience_id`; set it in the config before exporting.
* **`raw_uid2: null` on minted identities.** No salt was supplied for that member's bucket — fetch the salt from the operator and re-mint with the `salts` map, or accept the unresolved member until the operator round-trip completes.
* **`dispatch_skipped: true` with a populated list.** No export connection id resolved — wire `export_connection_id` and re-run; the skipped run is logged either way.
* **Members rejected as `duplicate`.** Two members normalized to the same identity — expected for Gmail dot/tag variants of one person. Only the first accepted occurrence mints.
* **`invalid_phone` rejections.** UID2 requires a leading `+` and 8–15 digits; national-format numbers without a country code never resolve.

## See also

* [CDP API reference](/api-reference/endpoints/cdp) — the endpoint schemas for config, mint, rotation, export, and runs
* [Create and connect your first audience](/guides/cdp-first-activation-end-to-end) — the deterministic activation loop UID2 extends to the open web
* [Ads activation](/guides/ads-activation) — the walled-garden path when the destination is one ad platform
* [Audience consent inspector](/guides/audience-consent-inspector) — verify consent basis before minting
* [Ads attribution and lead ingestion](/concepts/ads-attribution-lead-ingestion) — how ad-driven activity joins contacts and revenue
