This guide provides technical instructions for data engineers to configure and operate the predefined UID2 workflow within the Treasure Data platform.
The primary goal is to take your first-party Personally Identifiable Information (PII), such as email addresses and phone numbers, and convert it into privacy-safe Unified ID 2.0 (UID2) identifiers for use in the advertising ecosystem.
From The Trade Desk - UID2 Overview | Unified ID 2.0 UID2 is a framework that enables deterministic identity for advertising opportunities on the open Internet for many participants across the advertising ecosystem. The UID2 framework enables logged-in experiences from publisher websites, mobile apps, and Connected TV (CTV) apps to monetize through programmatic workflows. Built as an open-source, standalone solution with its unique namespace, the framework offers the user transparency and privacy controls designed to meet local market requirements.

Some of the business values from this integration are below.
- Use a privacy-conscious form of customer data in media-buying platforms.
- Manage frequency and suppress audiences across channels and devices.
- Support identity use cases where cookies don’t exist.
- Offer opt-out, with the goal of improving consumer privacy controls.
- Basic knowledge of Treasure Data
- Authorized Treasure Data account access
- Basic knowledge of UID2
- https://www.thetradedesk.com/us/about-us/industry-initiatives/unified-id-solution-2-0
- https://unifiedid.com/docs/intro or if you are in the EU: https://euid.eu/docs/intro
- Please refer to the About EUID section
| Term | Description |
|---|---|
| DII | DII is Directly Identifying Information, currently Email Addresses and Phone Numbers. |
| DII Normalization | DII (email address & phone numbers) must be normalized to the expected format per Normalization Standards. All DII will be mapped by a UID2 Service Operator as long as it is in the expected format as detailed below. Note that the UID2 Service Operator will map any email address or phone number as long as it is in the expected format, the email/phone does not need to be an actual live or working DII. - Email addresses must be normalized per the Email Normalization Standard. - Phone numbers must be in E.164 format. - Timestamps must be in ISO 8601 format. |
Before setting up this connector to start UID2 integrations, users need to have access to UID2 by submitting an access request through https://unifiedid.com/request-access.
Please work with your Trade Desk Account Manager or the Marketing Agency that owns the Trade Desk Account.
Once users have access to the UID2 service, they will have UID2 credentials that are an API key and a client secret to access the UID2.
You can get more information at https://unifiedid.com/docs/getting-started/gs-credentials#api-key-and-client-secret
AUID2 Base URL:
- In EU: https://euid.eu/docs/getting-started/gs-environments
- Others: https://unifiedid.com/docs/getting-started/gs-environments
Users can find the advertiser ID and secret key from the management console within The Trade Desk. Open “Preferences”, and then “First Party Data Credentials” to view the advertiser ID and secret key.
- Advertiser ID: The advertiser ID for your account with The Trade Desk.
- Advertiser secret: The advertiser secret for your account with The Trade Desk.
The UID2 service accepts DII(email and phone number currently) to convert UID2. Each piece of identifying information should follow a specific data format for conversion. Please review the Terminology section or https://unifiedid.com/docs/getting-started/gs-normalization-encoding for more details.
The UID2 workflow automates the process of generating and maintaining UID2s. At a high level, the process is as follows:
- Input:
- The workflow reads hashed or unhashed email addresses and phone numbers from source tables you specify within your Treasure Data database.
- Processing:
- It communicates with the UID2 Operator Service to securely map your PII to UID2s.
- The workflow automatically detects UID2s that have expired or reached their
refresh_fromtime (i.e., are considered stale by the UID2 service) and remaps them to fresh, active UID2s.
- Output:
- The final, valid UID2s are written to a central table in your database (ttd_uid2_ids)
There is a rate limit in the UID2 API endpoint; customers should consider limiting the number of records to process by setting the parameter uid2_limit_record
The workflow requires several tables to operate. You must create some, while others are managed automatically by the workflow.
The API in your target database creates the necessary staging tables for API responses.
ttd_uid2_resp: a staging table that hold the response of uid2 mapping
Or you can run the following SQL queries
CREATE TABLE IF NOT EXISTS "ttd_uid2_resp"("identifier" varchar);- ttd_uid2_ids: The main output table containing your source data mapped to its current, valid UID2.
- ttd_uid2_ids_archive: An archive of records from ttd_uid2_ids.
- ttd_uid2_rqst: A staging table that holds the JSON payloads for batch requests sent to the UID2 Operator.
We can create a predefined UID2 using the API
API Request
curl -L 'https://integrations-gateway.us01.treasuredata.com/integration_workflow/workflows/uid2/' \
-H 'Authorization: {Your_TD_API_KEY}' \
-H 'Content-Type: application/json' \
--data-raw '{
"type": "uid2",
"plugin_config": {
"type": "uid2",
"host": "https://operator-integ.uidapi.com",
"apikey": "****",
"secret": "****",
"td_apikey": "****",
"database": "your_database",
"td_host": "api-production.treasuredata.com",
"uid2_limit_records": 200000000,
"enable_auto_format": true,
"skip_limit": 500,
"mapping_uid2_bulkload_session_name": "uid2_bulkload_session_name",
"td_uid2_src_lst": [
{
"src_db": "src_db",
"src_tbl": "src_tbl",
"src_id_col": "contact_id",
"src_dii_col": "email",
"src_dii_typ": "EMAIL"
}
],
"custom_batch_size": 5000,
"custom_email_filter_import_uid2_identifier": "regexp_like(identifier, '''^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,}$''')",
"custom_phone_filter_import_uid2_identifier": "NOT regexp_like(identifier, '''^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,}$''')"
},
"workflow_config": {
"project_name": "project_name",
"revision": "revision",
"schedule": "daily>: 07:00:00",
"timezone": "America/Los_Angeles"
}
}'API Response
{
"id": "your workflow id",
"name": "your workflow project name",
"revision": "your workflow project revision",
"createdAt": "2025-07-01T09:55:28Z",
"updatedAt": "2025-07-03T07:23:35Z",
"deletedAt": null,
"archiveType": "s3",
"archiveMd5": "z2c8UL26Vl+uJGfR9wPmKA==",
"metadata": []
}Workflow config
| Name | Description | Value | Default Value | Required |
|---|---|---|---|---|
| project_name | Project name for workflow Please use a different project_name for each UID2 setup | N/A | N/A | Yes |
| revision | Project revision Using a different revision may override the existing project if there is a project with the same name | N/A | N/A | Yes |
| schedule | Schedule for workflow Should be daily with time You can find support values and detailed instructions in Treasure Data’s documentation here: 🔗 Scheduling Workflows – Treasure Data Docs | N/A | N/A | Yes |
| timezone | The timezone that the schedule should be set up in | N/A | UTC | No |
| Name | Description | Value | Default Value | Required |
|---|---|---|---|---|
| type | The plugin type | uid2 | N/A | Yes |
| host | The uid2 host Get from https://unifiedid.com/docs/getting-started/gs-environments | N/A | N/A | Yes |
| apikey | The UID2 API key | N/A | N/A | Yes |
| secret | The UID2 API client secret | N/A | N/A | Yes |
| database | Your database name in TD | N/A | N/A | Yes |
| td_apikey | Your TD API key | N/A | N/A | Yes |
| td_host | TD API host | N/A | N/A | Yes |
| mapping_uid2_bulkload_session_name | Your previous bulkload mapping session. Use when you want to migrate to a new workflow but keep the bulkload session for incremental load | N/A | N/A | No |
| td_uid2_src_lst | List of DII table, imported for mapping UID2 | N/A | N/A | Yes |
| skip_limit | The maximum number of invalid DII that we can ignore and process further | N/A | 500 | No |
| uid2_limit_records | The maximum number of DII that are sent to map UID2 | N/A | 2.000.000.000 | No |
| custom_batch_size | The number of DII groups in one request | N/A | 10000 | No |
| enable_auto_format | Enable this option to auto-hash the DII email value | N/A | true | No |
| custom_email_filter_import_uid2_identifier | Format validation rule for an email. Emails that meet the validation rules will be used for converting to UID2. Format validation rule for email. Emails that meet the validation rules will be used for converting to UID2. | N/A | regexp_like(identifier, '''^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,}$''') | No |
| custom_phone_filter_import_uid2_identifier | Format validation rule for a phone number. Phone numbers that meet the validation rules will be used for converting to the UID2Format validation rule for phone numbers. Phone numbers that meet the validation rules will be used for converting to UID2. | N/A | NOT regexp_like(identifier, '''^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,}$''') | No |
| custom_email_hash_filter_import_uid2_identifier | Format validation rule for a hashed email. Hashed emails that meet the validation rules will be used for converting to the UID2 format validation rule for hashed email. Hashed emails that meet the validation rules will be used for converting to UID2 | N/A | regexp_like(identifier, '^[a-zA-Z0-9+/]+={0,2}$') | No |
| custom_phone_hash_filter_import_uid2_identifier | Format validation rule for a hashed phone number. Hashed phone numbers that meet the validation rules will be used for converting to UID2.Format validation rule for a hashed phone number. Hashed phone numbers that meet the validation rules will be used for converting to UID2. | N/A | regexp_like(identifier, '^[a-zA-Z0-9+/]+={0,2}$') | No |
| last_uid2_request_time | Store the last UID2 request time for the next run. That value is used to select records in the ttd_uid2_rqst table | N/A | N/A | No |
| last_uid2_request_index | Store the last UID2 request index for the next run. That value is used to select records in the ttd_uid2_rqst table | N/A | N/A | No |
| Name | Description | Value | Default Value | Required |
|---|---|---|---|---|
| src_db | DII source database | N/A | N/A | Yes |
| src_tbl | DII source table | N/A | N/A | Yes |
| src_id_col | ID Column if available If no ID column, use the literal term "null" (w/o quotes) | N/A | N/A | Yes |
src_dii_col | The name of the Source DII column can be any name | N/A | N/A | Yes |
src_dii_typ | The type of DII - EMAIL - PHONE - EMAIL_HASH - PHONE_HASH | N/A | N/A | Yes |
Once created, you can access and monitor the workflow from the TD Console:
- Navigate to
- Data Workbench > Workflows.
- Search for the project_name you defined in the API call.
Please select the project to view its
Run History, where you can check the status, duration, and logs for each run.


– Transactional Table – Main TTD UID2 Table
| COLUMN | TYPE | DESCRIPTION |
|---|---|---|
time | INTEGER | Unixtime of record INSERT |
src_db | VARCHAR | The source database of the DII value |
src_tbl | VARCHAR | The source table of the DII value |
src_id_col | VARCHAR | The ID column for the source table of the DII value |
src_id | VARCHAR | The ID value of the record in the source table of the DII value |
src_col | VARCHAR | The Source column in the source table of the DII value |
src_typ | VARCHAR | The type of DII, one of {EMAIL, PHONE} |
src_data | VARCHAR | The source DII value |
advertising_id | VARCHAR | The UID2 value (Defined as advertising_id in Service Operator Service APIs) |
refresh_from | INTEGER | The time in epoch when the UID should be rotated. Start with 0. |
is_current | INTEGER | Indicates whether the UID2 (advertising_id column) contains a current UID2 value mapped from DII. - 0 (zero) – NO – Indicates that the ttd_uid2_ids record is new or does not have a current UID2 value in the advertising_id column. In either case, a new UID2 must be fetched from The Trade Desk (TTD). - 1 (one) – YES – Indicates that the ttd_uid2_ids record has a current UID2 value in the advertising_id column, so a new UID2 does NOT need to be fetched from TTD. The is_current state is managed during each workflow (WF) run and should always have the value 1 (one) for all records after every successful WF run. If any records have the value 0 (zero) after the WF run has completed, that means that something failed. The two primary causes of DII ↔︎ UID2 mapping failure are: - The DII format is not correct and therefore cannot be mapped by the UID2 Service Operator. For example, the email is not a valid email format (the domain is missing a TLD extension) and cannot be mapped by the Operator. Phone numbers must be in a valid E.164 format. Note that the Operator will map any email address or phone number as long as it is in the expected format; the email/phone does not need to be an actual live or working DII. - The TD UID2 Mapping Workflow failed for any reason. |
Same schema as ttd_uid2_ids table, except that the is_current will always have the value -1 to indicate archive records.
| COLUMN | TYPE | DESCRIPTION |
|---|---|---|
time | INTEGER | Unixtime of record INSERT |
src_db | VARCHAR | The source database of the DII value |
src_tbl | VARCHAR | The source table of the DII value |
src_id_col | VARCHAR | The ID column for the source table of the DII value |
src_id | VARCHAR | The ID value of the record in the source table of the DII value |
src_col | VARCHAR | The Source column in the source table of the DII value |
src_typ | VARCHAR | The type of DII, one of {EMAIL, PHONE} |
src_data | VARCHAR | The source DII value |
advertising_id | VARCHAR | The TTD UID2 value (Defined as advertising_id in Operator Service API's) |
refresh_from | INTEGER | Unix epoch seconds; refresh/remap when current time >= refresh_from. 0 indicates no refresh scheduled yet. |
is_current | INTEGER | Always has the value -1 (negative-one) to indicate archived records. |
| COLUMN | TYPE | DESCRIPTION |
|---|---|---|
time | INTEGER | Unixtime of record INSERT |
rnk_num | LONG | The sequence number of this UID2 Service Operator API batch request |
ttd_uid2_rqst | VARCHAR | The actual JSON payload for the UID2 Service Operator API batch request. It is a logical and valid JSON, stored VARCHAR for simplicity and convenience. It is stored as plain-text unencrypted; the TD Python client script manages all security and encryption/decryption internally. |
| COLUMN | TYPE | DESCRIPTION |
|---|---|---|
time | INTEGER | Unixtime of record INSERT |
rnk_num | LONG | The sequence number of this UID2 Service Operator API batch request (not used by this WF, for analysis purposes). |
identifier | VARCHAR | The DII value, either an Email or Phone |
advertising_id | VARCHAR | The UID2 value (Defined as advertising_id in Operator Service API's) |
refresh_from | INTEGER | Unix timestamp in seconds (epoch seconds) indicating the earliest time from which this UID2 should be refreshed/rotated. A value of 0 means no refresh time is set or provided. |
unmapped_reason | VARCHAR | The unmap reason for UID2 mapping |
After the UID2 workflow runs successfully, users will have UID2 in the specified destination database.
- ttd_uid2_ids table has advertising_id. The value of advertising_id is UID2
Users incorporate the ID value into the audience data, then activate segmentation through the export integration with Trade Desk based on UID2 ID values.
The Trade Desk Export Integration
Instead of creating the UID2 workflow via API, the user can create the UID2 workflow by using the workflow.
There are two options to define and run a workflow
- Use the graphical interface on TD Console > Data Workbench > Workflows
- Use the command-line interface of TD Toolbelt
While using the TD Console is more straightforward than the command-line interface, the TD CLI tool offers options for more complex configurations.
Refer here for more details about the syntax and usage of the TD Workflow
_export:
# Base configuration
gateway_url: https://integrations-gateway.us01.treasuredata.com/integration_workflow/workflows/uid2/
uid2_apikey: {uid2_apikey}
uid2_secret: {uid2_secret}
td_apikey: {td_apikey}
host: {host}
td_host: "api.treasuredata.com",
# Database configuration
database_name: {database_name}
# Project configuration
project_name: {project_name}
revision: {project_revision}
+trigger_uid2_workflow:
+prepare_request:
echo>: "Preparing HTTP request to UID2 integration gateway"
+execute_uid2_call:
http>: ${gateway_url}
method: POST
headers:
- Authorization: "TD1 ${td_apikey}"
- Content-Type: "application/json"
content: |
{
"type": "uid2",
"plugin_config": {
"type": "uid2",
"host": "${host}",
"apikey": "${uid2_apikey}",
"secret": "${uid2_secret}",
"database": "${database_name}",
"td_apikey": "${td_apikey}",
"td_host": "${td_host}",
"uid2_limit_records": 2000000000,
"enable_auto_format": true,
"skip_limit": 500,
"td_uid2_src_lst": [
{
"src_db": "source_database",
"src_tbl": "email_table",
"src_id_col": "129212",
"src_dii_col": "userid",
"src_dii_typ": "EMAIL"
}
],
"custom_batch_size": 5000,
"custom_email_filter_import_uid2_identifier": "regexp_like(identifier, '^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$')",
"custom_phone_filter_import_uid2_identifier": "NOT regexp_like(identifier, '^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$')"
},
"workflow_config": {
"project_name": "${project_name}",
"revision": "${revision}",
"schedule": "daily>: 07:00:00",
"timezone": "America/Los_Angeles"
}
}
retry: 3
store_content: true
+echo_result:
if>: ${http.last_content != null}
_do:
echo>: "Response details: ${http.last_content}"
+error_handling:
_error:
echo>: "Error triggering UID2 workflow. Check logs for details."| Name | Description | Value | Default Value | Required |
|---|---|---|---|---|
| project_name | Project name for workflow Please use a different project_name for each UID2 setup | N/A | N/A | Yes |
| revision | Project revision Using a different revision may override the existing project if there is a project with the same name | N/A | N/A | Yes |
| schedule | Schedule for workflow Should be daily with time You can find support values and detailed instructions in Treasure Data’s documentation here: 🔗 Scheduling Workflows – Treasure Data Docs | N/A | N/A | Yes |
| timezone | The timezone that the schedule should be set up in | N/A | UTC | No |
| Name | Description | Value | Default Value | Required |
|---|---|---|---|---|
| type | The plugin type | uid2 | N/A | Yes |
| host | The uid2 host Get from https://unifiedid.com/docs/getting-started/gs-environments | N/A | N/A | Yes |
| apikey | The UID2 API key | N/A | N/A | Yes |
| secret | The UID2 API client secret | N/A | N/A | Yes |
| database | Your database name in TD | N/A | N/A | Yes |
| td_apikey | Your TD API key | N/A | N/A | Yes |
| td_host | TD API host | N/A | N/A | Yes |
| mapping_uid2_bulkload_session_name | Your previous bulkload mapping session. Use when you want to migrate to a new workflow but keep the bulkload session for incremental load | N/A | N/A | No |
| td_uid2_src_lst | List of DII table, imported for mapping UID2 | N/A | N/A | Yes |
| skip_limit | The maximum number of invalid DII that we can ignore and process further | N/A | 500 | No |
| uid2_limit_records | The maximum number of DII that are sent to map UID2 | N/A | 2.000.000.000 | No |
| custom_batch_size | The number of DII groups in one request | N/A | 10000 | No |
| enable_auto_format | Enable this option to auto-hash the DII email value | N/A | true | No |
| custom_email_filter_import_uid2_identifier | Custom email conditions to import into ttd_uid2_ids | N/A | regexp_like(identifier, '''^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,}$''') | No |
| custom_phone_filter_import_uid2_identifier | Custom phone conditions to import into ttd_uid2_ids | N/A | NOT regexp_like(identifier, '''^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,}$''') | No |
| custom_email_hash_filter_import_uid2_identifier | Custom email_hash conditions to import into ttd_uid2_ids | N/A | regexp_like(identifier, '^[a-zA-Z0-9+/]+={0,2}$') | No |
| custom_phone_hash_filter_import_uid2_identifier | Custom phone_hash conditions to import into ttd_uid2_ids | N/A | regexp_like(identifier, '^[a-zA-Z0-9+/]+={0,2}$') | No |
| last_uid2_request_time | Store the last UID2 request time for the next run. That value is used to select records in the ttd_uid2_rqst table | N/A | N/A | No |
| last_uid2_request_index | Store the last UID2 request index for the next run. That value is used to select records in the ttd_uid2_rqst table | N/A | N/A | No |
| Name | Description | Value | Default Value | Required |
|---|---|---|---|---|
| src_db | DII source database | N/A | N/A | Yes |
| src_tbl | DII source table | N/A | N/A | Yes |
| src_id_col | ID Column if available If no ID column, use the literal term "null" (w/o quotes) | N/A | N/A | Yes |
src_dii_col | The name of the Source DII column can be any name | N/A | N/A | Yes |
src_dii_typ | The type of DII - EMAIL - PHONE - EMAIL_HASH - PHONE_HASH | N/A | N/A | Yes |
EUID is an open-source, standalone solution with its own unique namespace that builds on the UID2 framework. The main differences between UID2 and EUID result from more stringent European and UK data protection laws relating to consent practices, rights for data subjects, and obligations between participants. Otherwise, EUID follows the same guiding principles as UID2.
Reference: https://euid.eu/docs/intro
| Comparison Aspect | UID2 | EUID |
|---|---|---|
| Open-sourced framework | Yes | Yes |
| Interoperability | Yes | Yes |
| Personal data used | Email addresses, phone numbers | Email addresses, phone numbers |
| Consent | Based on local regulations such as the California Privacy Rights Act (CPRA) and the California Consumer Privacy Act (CCPA). | Driven by the General Data Protection Regulation (GDPR), the Transparency and Consent Framework (TCF) operated by IAB, and local regulatory guidance. |
All EUID, use a different base URL: https://euid.eu/docs/getting-started/gs-environments