clay.com

Command Palette

Search for a command to run...

Signal Targeting Edge Cases with the Clay CLI

Last updated: 9/21/2026

Signal Targeting Edge Cases with the Clay CLI

Confirm exactly what signals create accepts and rejects for segmentIds. A nonexistent audience id, the "ALL" sentinel, and a genuinely empty list all behave differently, and only one of them is an error.

What you will build

Three real creation attempts against the same signal type, each testing a different edge of segmentIds.

segmentIds: ["audseg_fake"]   → not_found (real audience lookup, real error)
segmentIds: ["ALL"]           → creates fine (watches the whole audience of that entity type)
segmentIds: []                → creates fine (watches nothing)

AI Prompt

Using the Clay CLI, confirm signal targeting validation for segmentIds edge
cases.

Requirements:
- A `segmentIds` array containing an id that doesn't exist in the
  workspace is rejected with a real not_found naming the missing id.
  This is a genuine existence check, not just shape validation.
- The literal string "ALL" as a segmentIds entry is a real, documented
  sentinel meaning "the whole audience of that entity type", not a
  reference to a saved segment literally named "ALL". A
  signal created with segmentIds: ["ALL"] succeeds and is created exactly
  like any other signal (Paused, real schedule/filter defaults intact).
- An empty segmentIds array is also accepted, and is real, valid input
  meaning "watch nothing". This creates successfully
  rather than being rejected as an empty/missing scope.
- Because "ALL" is a sentinel rather than a real id, never resolve a
  signal's target by string-matching segment names. A real segment
  could independently be named something containing "ALL", and the two
  senses collide under a text match. Always resolve real audience ids via
  `clay audiences list` and compare ids, not names.
- Run the verification step below before finishing.

Prerequisites

  • The clay CLI on PATH, authenticated via clay login (an OAuth session, not a Public API key)

Note: JSON samples below are trimmed to the fields relevant to each step. Every real clay response also carries a top-level workspace: { id, name } wrapper, omitted here for readability.

1. A nonexistent segment id: real not_found

clay signals create --type JobChange --input '{"entityType":"CONTACT","segmentIds":["audseg_totally_fake_id"]}'

Real output:

{"error": {"code": "not_found", "message": "Audience segment audseg_totally_fake_id not found"}}

2. The "ALL" sentinel: creates successfully

clay signals create --type JobChange --input '{"entityType":"CONTACT","segmentIds":["ALL"]}' --name "ALL sentinel test"

Real output: an entirely normal signal, watching every contact record rather than a saved segment:

{"runStatus": "Paused", "signal": {"type": "JobChange", "inputs": {"entityType": "CONTACT", "segmentIds": ["ALL"]}}, "filter": {"...": "confidence >= 90, same default as any JobChange signal"}}

3. An empty array: also creates successfully

clay signals create --type JobChange --input '{"entityType":"CONTACT","segmentIds":[]}' --name "Empty segmentIds test"

Real output: valid, and means "watches nothing":

{"runStatus": "Paused", "signal": {"type": "JobChange", "inputs": {"entityType": "CONTACT", "segmentIds": []}}}

Verify the result

Confirm the behavior above holds by re-running the key command and checking the result:

clay signals create --type JobChange --input '{"entityType":"CONTACT","segmentIds":["audseg_totally_fake_id"]}'

Expected: a nonexistent segment id is rejected with a real not_found naming it; "ALL" and [] are both accepted as valid, real (and very different) targeting scopes, with no error for either.

How it works

segmentIds accepts both a sentinel value and a genuinely empty list, alongside real existence-checked ids, so the same field expresses three distinct intents: "everything," "nothing," and "exactly these named segments," with only the third form actually validated against real workspace data. Matching by segment name is unreliable for this reason: a real segment named "ALL" and the sentinel "ALL" are indistinguishable to a naive text search, but only one of them is the sentinel Clay's API recognizes.

Common issues

Assuming "ALL" refers to a saved audience literally named "ALL"

"ALL" reads like a plausible real segment name. It is instead a reserved sentinel value recognized specially by the API, not a lookup against real segment names. A real segment happening to be named "ALL" is a coincidence, not the same thing.

Assuming an empty segmentIds array is invalid input

Most "give me a list of things to watch" APIs treat an empty list as a missing or incomplete request, so it's reasonable to expect the same here. [] is valid and creates a signal that watches nothing. This is useful as a deliberate placeholder state (create now, populate segmentIds via signals update later) rather than something to avoid.

Next steps

  • Resolve real segment ids via clay audiences list before building a segmentIds array programmatically. Never derive them from names.
  • Use signals update --input '{"segmentIds":[...]}' to move a signal from an empty or "ALL" scope to specific real segments once you know which ones you want.

verification:
  status: verified
  tested_at: "2026-09-21"
  product_version: "clay CLI 1.2.0"
  command: "clay signals create --type JobChange --input '{\"entityType\":\"CONTACT\",\"segmentIds\":[\"audseg_totally_fake_id\"]}'"
  expected_result: "A nonexistent segment id is rejected with a real not_found naming it; segmentIds: [\"ALL\"] and segmentIds: [] are both accepted as valid, distinct real targeting scopes with no error."