Complete workflow to set up RT 2.0 personalization from scratch - validates parent segment RT status, discovers event tables and attributes, configures RT infrastructure (events, attributes, ID stitching), creates personalization service, and deploys the personalization entity with payload. Use when user wants to "create realtime setup and build personalization" or "set up RT personalization end-to-end". Includes automatic validation using rt-personalization-validation skill to prevent common API errors.
85%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
Passed
No findings from the security scan
Orchestrates the complete RT personalization setup: parent segment validation → use case discovery → data exploration → RT config → personalization service → personalization entity deployment.
tdxtdx auth setupexport TD_API_KEY=your_keyComplete 9-step workflow. Each step must complete successfully before proceeding.
Step 1: Validate Parent Segment ✓
Step 2: Use Case Discovery ✓
Step 3: Explore PS Attributes ✓
Step 4: Discover Event Tables ✓
Step 5: Define ID Stitching ✓
Step 6: Define RT Attributes ✓
Step 7: Configure RT Infrastructure ✓ (SHARED with rt-setup-triggers)
Step 8: Create Personalization Service ✓
Step 9: Create Personalization Entity ✓
9a. Get Parent Segment Folder
9b. Get Key Event and Attribute IDs
9c. Validate Payload (REQUIRED - use rt-personalization-validation skill)
9d. Create Entity via API
Step 10: Verify & Test ✓See steps/01-02-validate-discovery.md for detailed implementation.
Step 1: Validate parent segment exists and check RT status
# Check if PS has RT enabled
tdx ps rt list --json | jq '.[] | select(.id=="<ps_id>" or .name=="<ps_name>") | {
id, name,
rt_status: .status,
event_tables: (.event_tables | length),
key_events: (.key_events | length)
}'Expected outputs:
rt_status: "ok" → RT enabled, proceed to Step 2rt_status: "updating" → Wait for RT to be readyStep 2: Ask user about use case (Web Personalization, Cart Recovery, User Profile API, etc.)
Checkpoints:
See steps/03-06-data-exploration.md for detailed implementation.
Step 3: Explore parent segment batch attributes
# List all RT-enabled parent segments
tdx ps rt list --json
# Show RT-enabled parent segments with status
tdx ps rt list --json | jq '.[] | {
id, name,
rt_status: .status,
event_tables: (.event_tables | length),
key_events: (.key_events | length)
}'Step 4: Discover streaming event tables
tdx tables "<event_db>.*" --json | jq -r '.[].name'Step 5: Define ID stitching keys (td_client_id, email, canonical_id) — only use columns that exist in event tables (verify with tdx describe
)Step 6: Define RT attributes based on use case (single, list, counter types)
Checkpoints:
SHARED CONFIGURATION - See steps/07-rt-config.md for complete implementation.
This step is identical for both RT Personalization and RT Triggers.
7a. Configure Event Tables & Key Events
# Add event tables
tdx api "/audiences/<ps_id>/realtime_setting" --type cdp -X PATCH --data '{
"eventTables": [{"database": "<db>", "table": "<table>"}]
}'
# Create key events
tdx api "/audiences/<ps_id>/realtime_key_events" --type cdp -X POST --data '{
"name": "<event_name>",
"databaseName": "<db>",
"tableName": "<table>",
"filterRule": {"type": "And", "conditions": []}
}'7b. Configure ID Stitching
⚠️ CRITICAL: extLookupKey must be the Profile Key column from the Parent Segment master table (e.g., guest_id, customer_id), NOT a stitching key like email. Find it with:
tdx ps desc <ps_id> --json | jq '.master'
```bash
tdx api "/audiences/<ps_id>/realtime_setting" --type cdp -X PATCH --data '{
"keyColumns": [
{"name": "td_client_id", "validRegexp": null, "invalidTexts": [], "internal": false}
],
"extLookupKey": "<profile_key>"
}'7c. Create RT Attributes
# Single attribute
tdx api "/audiences/<ps_id>/realtime_attributes" --type cdp -X POST --data '{
"name": "<attr_name>",
"type": "single",
"realtimeKeyEventId": "<key_event_id>",
"valueColumn": "<column>",
"dataType": "string",
"duration": {"value": 1, "unit": "day"}
}'
# List attribute
tdx api "/audiences/<ps_id>/realtime_attributes" --type cdp -X POST --data '{
"name": "<list_attr>",
"type": "list",
"realtimeKeyEventId": "<key_event_id>",
"idColumn": "<id_column>",
"maxItems": 100,
"aggregations": [{
"name": "items",
"identifier": "items",
"column": "<column>",
"aggregationType": "distinct_list"
}],
"duration": {"value": 60, "unit": "day"}
}'
# Counter attribute
tdx api "/audiences/<ps_id>/realtime_attributes" --type cdp -X POST --data '{
"name": "<counter_attr>",
"type": "counter",
"realtimeKeyEventId": "<key_event_id>",
"counterType": "total",
"increment": {"type": "const", "value": 1},
"duration": {"value": 24, "unit": "hour"}
}'Wait for RT status "ok":
while [ "$(tdx ps rt list --json | jq -r --arg ps '<ps_id>' '.[] | select(.id==$ps) | .status')" != "ok" ]; do
echo "Waiting for RT infrastructure..."
sleep 10
done
echo "✅ RT infrastructure ready"Checkpoints:
CRITICAL: Personalization entity creation has strict validation rules. The most common error is:
Error: "sections[0].payload.node_id.definition.attribute_payload": ["Attribute payload can't be blank"]Root cause: Using empty arrays [] instead of null for unused fields.
Quick fix:
// ❌ FAILS
"stringBuilder": []
"segmentPayload": []
"keyEventFilters": {"type": "And", "conditions": []}
// ✅ WORKS
"stringBuilder": null
"segmentPayload": null
"keyEventFilters": nullREQUIRED VALIDATION STEP:
At Step 9c, you MUST invoke the rt-personalization-validation skill to validate the payload before making the API call. This is a mandatory step in the workflow, not optional. The validation skill will:
Do not skip Step 9c validation. Proceeding directly to the API call without validation will likely result in errors.
See steps/08-09-personalization.md for detailed implementation.
Step 8: Create personalization service via tdx
cat > pz_service.yaml << 'EOF'
parent_segment_id: '<ps_id>'
personalization_service:
name: '<use_case>_personalization'
trigger_event: '<key_event_name>'
sections:
- name: 'Default'
criteria: ''
attributes: [last_product_viewed, page_views_24h]
EOF
tdx ps push pz_service.yaml -yStep 9: Create personalization entity via API (with payload)
# Generate unique payload node ID
PAYLOAD_NODE_ID=$(uuidgen | tr -d '-' | tr '[:upper:]' '[:lower:]')
# Get folder ID
FOLDER_ID=$(curl -s "https://api-cdp.treasuredata.com/audiences/<ps_id>/folders" \
-H "Authorization: TD1 ${TD_API_KEY}" | jq -r '.[0].id')
# Create entity
curl -X POST 'https://api-cdp.treasuredata.com/entities/realtime_personalizations' \
-H "Authorization: TD1 ${TD_API_KEY}" \
-H 'Content-Type: application/vnd.treasuredata.v1+json' \
--data "{
\"attributes\": {
\"audienceId\": \"<ps_id>\",
\"name\": \"<use_case>_personalization\",
\"sections\": [{
\"name\": \"Default_Section\",
\"entryCriteria\": {
\"keyEventCriteria\": {
\"keyEventId\": \"<key_event_id>\",
\"keyEventFilters\": null
},
\"profileCriteria\": null
},
\"payload\": {
\"$PAYLOAD_NODE_ID\": {
\"type\": \"ResponseNode\",
\"definition\": {
\"attributePayload\": [
{\"realtimeAttributeId\": \"<attr_id>\", \"outputName\": \"last_product\"}
],
\"segmentPayload\": null,
\"stringBuilder\": null
}
}
},
\"includeSensitive\": false
}]
},
\"relationships\": {
\"parentFolder\": {\"data\": {\"id\": \"<folder_id>\", \"type\": \"folder-segment\"}}
}
}"Checkpoints:
See steps/10-verification.md for complete verification checklist.
After setup completes, verify:
# 1. RT status is "ok"
tdx ps rt list --json | jq -r --arg ps "<ps_id_or_name>" '.[] | select(.id==$ps or .name==$ps) | .status'
# Expected: "ok"
# 2. Key events exist
tdx api "/audiences/<ps_id>/realtime_key_events" --type cdp | jq '.data | length'
# Expected: > 0
# 3. RT attributes exist
tdx api "/audiences/<ps_id>/realtime_attributes?page[size]=100" --type cdp | jq '.data | length'
# Expected: > 0
# 4. Configuration deployed
curl -s "https://api-cdp.treasuredata.com/entities/parent_segments/<ps_id>/realtime_personalizations" \
-H "Authorization: TD1 ${TD_API_KEY}" | jq '.data | length'
# Expected: > 0
# 5. API endpoint responds
curl -X GET "https://${REGION}.p13n.in.treasuredata.com/audiences/<ps_id>/personalizations/<pz_id>?td_client_id=test_user" \
-H "Authorization: TD1 ${TD_API_KEY}"
# Expected: JSON with attributes (not 404)Console URL:
https://console-next.${REGION}.treasuredata.com/app/ps/<ps_id>/e/<pz_id>/p/deIf any check fails, review the corresponding setup step.
This orchestrator ensures all checkpoints are met:
For RT Triggers (Journeys) instead, use the rt-setup-triggers skill which shares Steps 1-7.
RT not enabled:
❌ RT 2.0 is not enabled for this parent segment.
→ Contact your CSM to enable RT 2.0.No RT-enabled parent segments:
❌ No RT-enabled parent segments found in your account.
→ Contact your CSM to enable RT 2.0 for a parent segment.RT status "updating":
⚠️ RT configuration is updating. Waiting for status "ok"...
→ This typically takes 30-90 seconds.Missing event tables:
❌ No streaming event tables found in database "<db>".
→ Verify database name or check if events are being ingested.treasure-data/td-skills
d0e1a9d
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.