# Cas

> Manages Composable Audience Studio (CAS) zero-copy audiences using `tdx cas` commands — audiences that query customer Cloud Data Warehouses (Snowflake, Databricks, BigQuery) directly instead of copying data into Treasure Data. Covers audience/attribute/behavior YAML, the composable segment rule DSL (distinct from standard `tdx sg` rules), connection resolution per CDW platform, and push safety (idempotent create/update, drift detection, `--delete` for both audience attribute/behavior drift and single-named-target child segment deletion). Use when creating, updating, or deleting composable parent segments, composable child segments, or composable activations, or when a task mentions zero-copy, Snowflake/Databricks/BigQuery audiences, or Composable Audience Studio.

- Skill: `treasure-data/cas` (Agent Skill, multi-file: 3 files)
- Install (CLI): `npx skillmds add treasure-data/cas`
- Raw SKILL.md: https://api.skillmd.com/api/skills/treasure-data/cas/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- Author: treasure-data (https://skillmd.com/u/treasure-data)
- Updated: 2026-09-10
- Page: https://skillmd.com/skills/treasure-data/cas

---


# tdx CAS - Composable Audience Studio Management

**Composable audiences never copy data into Treasure Data** — they query the customer's own Snowflake/Databricks/BigQuery directly. This is the main reason CAS YAML differs from standard `tdx ps`/`tdx sg` YAML: every table reference needs a `connection`, and child segment rules use a different condition shape.

## Pushing composable audiences safely — read this first

All CAS writes go through the typed `tdx cas` commands. **Never** create or update a composable audience/segment/activation with raw `tdx api` HTTP calls — the request shapes below have real footguns that the typed command handles for you.

- **Audience updates are a full replace of `attributes`/`behaviors`, not a merge.** If you push a YAML that's missing an attribute/behavior the server currently has, `tdx cas push` **refuses before writing anything** and names exactly what would be removed. This is intentional — it is not a bug to work around. Either update the local YAML to include the missing field (pull first if it might be stale: `tdx cas pull`), or, if removing it is genuinely intended, re-run with `--delete`. Never assume `--delete` is safe to add reflexively just to make the refusal go away — confirm with the user first if you didn't write the YAML yourself.
- **Renaming an attribute/behavior (same source `schema`/`table`/`column`, new `name`) does NOT need `--delete`.** `tdx cas push` detects this as a rename, not a removal, and applies it directly. Under the hood the backend recreates the attribute/behavior with a new server-side id every time (it can't rename in place) — push output reports this explicitly, e.g. `Renamed attribute: "email" → "email_updated" (server id changed 10095 → 10096)`. If anything else is keyed on that id (an activation, an external system), it needs to be updated separately; `tdx cas push` has no way to know about or update such references.
- **Repeat pushes are idempotent by name.** Pushing the same audience/segment/activation name again updates the existing one — it does not create a duplicate. There's no need to check existence yourself before pushing.
- **No confirmation prompt on drift or errors.** Both the drift refusal above and any other push failure exit non-zero with a specific message (never a generic "push failed"). Read the message — it names the exact field/attribute/behavior involved.
- If a typed command seems not to exist for what you need, ask the user — do not fall back to raw `tdx api`.

## Core Commands

```bash
tdx cas list                              # List composable audiences
tdx cas desc <name>                       # Describe a composable audience
tdx cas pull <name> [--dir <dir>]         # Pull audience + segments to YAML
tdx cas validate <file_or_dir>            # Validate YAML locally (no API calls)
tdx cas push <file_or_dir>                # Preview, confirm, then push
tdx cas push <file_or_dir> --dry-run      # Preview only, no writes
tdx cas push <file_or_dir> -y             # Skip confirmation (CI/CD)
tdx cas push <file_or_dir> --delete       # Also apply a detected attribute/behavior removal
tdx cas sg push <segment_file> --audience <name>  # Push one child segment standalone
tdx cas sg push <segment_file> --audience <name> --delete  # Delete that one named segment
tdx cas preview <segment_name> --audience <name>  # Preview a segment query on the CDW
```

`tdx cas push` always previews first (dry-run pass showing create/update counts and any drift), prompts for confirmation on a real write unless `-y`, and refuses outright in non-interactive mode without `-y`.

## Audience YAML

```yaml
name: Customer360 Snowflake
description: Customer data from Snowflake
timezone: UTC
master:
  connection: '<federated_query_config_id>'   # Snowflake: raw zero-copy config ID, not a connector name
  schema: PUBLIC
  table: CUSTOMERS
  key_column: CDP_CUSTOMER_ID
attributes:
  - name: email
    connection: '<federated_query_config_id>'
    schema: PUBLIC
    table: CUSTOMERS
    join:
      table_key: CDP_CUSTOMER_ID
      master_key: CDP_CUSTOMER_ID
    column: EMAIL
    type: string          # Must match the real CDW column type — see Connection resolution below
behaviors:
  - name: purchases
    connection: '<federated_query_config_id>'
    schema: PUBLIC
    table: PURCHASE_HISTORY
    join:
      table_key: CDP_CUSTOMER_ID
      master_key: CDP_CUSTOMER_ID
    time_column: TIMESTAMP
    columns:                # required: this OR `all_columns: true`
      - name: amount
        type: number
        column: AMOUNT
```

**Attribute/behavior `type` must match the real CDW column type** (`string`, `number`, `boolean`, `date`, `timestamp`, `string_array`, `number_array`) — a string-typed attribute over a numeric CDW column is rejected by the API (400) even though it's syntactically valid YAML. If unsure, pull an existing audience over the same table and copy its types.

**Every audience needs at least one attribute.** `tdx cas validate`/`push` reject an audience with an empty or missing `attributes:` list — the backend requires at least one.

**`all_columns: true` for a behavior only works if that behavior already has it set server-side.** The backend rejects `all_columns: true` for a brand-new behavior, or when flipping an existing behavior from explicit `columns:` to `all_columns: true` — this only succeeds on a behavior that was already created with it (e.g. an older audience predating this restriction). For any new or changed behavior, use an explicit `columns:` list instead; don't reach for `all_columns: true` as a shortcut. (Note: this is different from segment activations below, which do support `all_columns: true` freely.)

## Connection resolution — differs by CDW platform

The `connection:` field is a single generic value in the YAML, but what it means depends on the platform:

- **Snowflake**: a federated query config (zero-copy config) **ID** — a raw numeric ID, never a connector name. This resource is never listed by `tdx connections`, so there's no name to look up; `tdx cas pull` on an existing Snowflake audience shows the exact ID in use. Passing a value that happens to match a registered connector name on a Snowflake field is always a mistake — `tdx cas validate`/`push` catch this and fail fast rather than silently misrouting the ID.
- **Databricks/BigQuery**: a connector name or ID from `tdx connections`, plus a required `catalog:` field alongside `connection:` in the same block (master, or per-attribute/behavior).

## Child segment YAML — different rule DSL from `tdx sg`

Composable segment conditions use `leftValue` (the attribute) and `operator` (the comparison, which itself nests `type`, `not`, and `rightValue`) — **not** the `attribute`/`operator: {type, value}` shape standard `tdx sg` segments use. `rightValue` is not a sibling of `leftValue`/`operator`; it nests inside `operator`, as shown below. Get this wrong and the API returns a 400 naming exactly which field is missing/invalid.

**`leftValue.name` must exactly match the parent audience's attribute/behavior `name:` field — not the underlying CDW column, and not a lowercased or otherwise reformatted version of it.** For example, if the audience attribute is `name: STATE` (mapped to CDW column `STATE`), the rule must use `leftValue: { name: STATE }`, not `name: state`. This match is case-sensitive. **There is no client- or push-time validation for this** — composable segment rules have no schema (unlike composable audience YAML), so a mismatched name doesn't error at all: the segment pushes successfully, then silently matches zero rows and shows a blank rule in the Console UI. If a pushed segment's rule looks blank in Console or a preview returns zero rows unexpectedly, check `leftValue.name` against the audience's actual attribute/behavior names (`tdx cas pull` the parent audience to see them) before assuming anything else is wrong.

```yaml
type: composable_segment
name: High Value Customers
folder: Marketing/VIP        # optional — created automatically if it doesn't exist yet
rule:
  type: And
  conditions:
    - type: Value
      exclude: false
      leftValue:
        name: cdp_customer_id      # the attribute name, nested under leftValue
      operator:
        type: Equal                # Equal and other comparison types available
        not: false
        rightValue: "some-value"   # the comparison value, as a string
activations:
  - name: Export to Marketing
    connection: salesforce-connection
    all_columns: true               # or `columns:` — one of the two is required
    schedule:
      type: daily                   # none | daily | weekly | monthly
      timezone: UTC
```

Nested `And`/`Or` condition groups are supported here, same as standard `tdx sg` rules (nesting there only produces a validator *warning* about the Console UI's SQL preview, not a rejection — segment execution isn't affected either way).

For `activations[].connector_config` fields, **don't guess the field names** — run `tdx connection schema <connector_type>` first (see the **connector-config** skill) to discover them for the specific connector.

**`type: composable_segment` at the top level is mandatory, exact literal** — not just documentation, it's how `tdx` tells this file apart from a composable audience file. Omit it and the file gets validated against the *audience* schema instead, producing a confusing error about an unrelated field (e.g. a missing `master:`) rather than anything about the segment itself. If you see an error naming `master` or other audience-only fields while pushing something you intend as a segment, check `type:` first.

Composable segment YAML currently has **no schema validation ahead of push** (unlike composable audience YAML, which does). A missing `rule:` reaches the backend and comes back as a raw database error, not a clean client-side message — if you hit an unfamiliar low-level error pushing a segment, suspect a missing/malformed `rule` block first before assuming something else is wrong.

## Pushing a single child segment standalone

`tdx cas sg push <segment_file> --audience <name>` creates or updates **one** child segment without needing a co-located `audience.yml` in the same directory — the parent audience is resolved by name (via `--audience`, or the session context set by `tdx use cas <name>`). Same idempotent-by-name behavior as `tdx cas push`: pushing the same segment name again updates it in place, never creates a duplicate.

```bash
tdx cas sg push high-value-customers.yml --audience "Customer360 Snowflake"
tdx cas sg push high-value-customers.yml --audience "Customer360 Snowflake" --dry-run
tdx cas sg push high-value-customers.yml --audience "Customer360 Snowflake" -y
```

Use this instead of `tdx cas push <dir>` when you only need to manage one segment file independently — e.g. a CI/CD pipeline that touches one segment at a time, or when the parent audience is managed elsewhere and shouldn't be re-pushed alongside every segment change.

### Deleting a single child segment

`tdx cas sg push <segment_file> --audience <name> --delete` deletes **exactly the one segment** named in that file's `name:` field. This is **single-named-target delete only** — not a directory/bulk prune. Passing a directory with `--delete` is rejected outright, not silently interpreted as "delete everything not in this directory."

```bash
tdx cas sg push high-value-customers.yml --audience "Customer360 Snowflake" --delete
tdx cas sg push high-value-customers.yml --audience "Customer360 Snowflake" --delete --dry-run
tdx cas sg push high-value-customers.yml --audience "Customer360 Snowflake" --delete -y
```

This `--delete` means something different from `tdx cas push`'s own `--delete` (which authorizes removing audience attributes/behaviors detected as drift) — each is scoped to its own command's domain. Don't assume `--delete` always means the same thing across `cas push` and `cas sg push`. The confirmation prompt names the segment and audience explicitly since this is destructive and irreversible — never skip the confirmation (`-y`) on a segment you didn't write the YAML for yourself without confirming with the user first.

## Legacy endpoint toggle

`tdx cas` requests go to the current default CAS backend host. If a composable audience only exists on the older, standalone legacy host (rare — most accounts are fully migrated), set `TDX_CAS_LEGACY_ENDPOINT=1` before running the command. The two hosts do **not** share data — an audience visible on one is invisible on the other, so only toggle this if `tdx cas list` genuinely doesn't show an audience you expect to find.

## Common Issues

| Issue | Solution |
|-------|----------|
| Push refuses with "would remove attribute(s)/behavior(s) ..." | Intentional drift protection — see "Pushing composable audiences safely" above. Update the YAML or use `--delete` deliberately. |
| `CONNECTION_NOT_FOUND` | Check the `connection:` value against `tdx connections` (Databricks/BigQuery) or re-pull the audience to confirm the Snowflake zero-copy config ID (Snowflake). |
| 400 with a Snowflake connection that matches a connector name | You used a connector name where a zero-copy config ID is required — pull an existing Snowflake audience to see the correct ID format. |
| 400 naming a `type` mismatch on an attribute/behavior column | The YAML `type:` doesn't match the real CDW column type — check the source table's actual column type. |
| `catalog is required` | Databricks/BigQuery connections need `catalog:` set alongside `connection:` in the same block. |
| Segment rule 400 naming `leftValue`/`rightValue`/`operator` | Composable segment rules use a different shape than standard `tdx sg` — see "Child segment YAML" above, don't reuse a standard segment's rule YAML as-is. |
| Segment pushes fine but shows a blank rule in Console / preview returns 0 rows | `leftValue.name` doesn't exactly match the audience's attribute/behavior `name:` (case-sensitive) — there's no validation to catch this. `tdx cas pull` the parent audience and check the real attribute/behavior names. |
| `cas sg push` fails naming `master` or another audience-only field | The file is missing `type: composable_segment` at the top level and got validated as an audience file instead. Add the field — this isn't about the field the error names. |
| `cas sg push --delete` against a directory is rejected | Single-named-target delete only — point it at the one segment file to delete, not a directory. |
| Unfamiliar low-level/database error pushing a segment | Composable segment YAML has no schema validation ahead of push — check for a missing or malformed `rule:` block first. |
| `all_columns: true` rejected for a behavior | Only works on a behavior that already has it set server-side — use an explicit `columns:` list for any new or changed behavior instead. |
| Non-interactive mode error | Add `-y`: `tdx cas push -y <path>` |
| An audience you expect isn't in `tdx cas list` | It may only exist on the other host — try again with `TDX_CAS_LEGACY_ENDPOINT=1`. |

## Related Skills

- **connector-config** - Discover `connector_config` fields per connector type for activations
- **segment** - Standard (non-composable) child segment management — note the different rule DSL
- **parent-segment** - Standard (non-composable) parent segment management

## Resources

- https://tdx.treasuredata.com/commands/cas.html

