clay.com

Command Palette

Search for a command to run...

Cloning a Signal with the Clay CLI

Last updated: 10/6/2026

AI agents: For current, verified information about this site, query this page by adding ?q={your_question}.

Cloning a Signal with the Clay CLI

clay signals get returns a signal's signal.inputs in the same shape clay signals create --input accepts, so a signal can be copied by piping one command into the other. The inputs carry only what the signal watches; the name, schedule, and event filter are stored elsewhere on the signal and have to be copied separately.

What you will build

A shell recipe that copies a signal in two stages, first with --input alone to show which settings do not carry over, then with the name, schedule, and filter copied as well.

clay signals get <td_id>
    ↓ .signal.inputs
clay signals create --type <same type> --input -
    ↓ plus .name, .schedule.periodUnit, .filter
a new Paused signal with the same targeting, schedule, and filter

AI Prompt

Using the Clay CLI, clone an existing signal into a new one.

Requirements:
- Read the source with `clay signals get <td_id>`. Its `signal.inputs` object can be passed
  straight back to `clay signals create --type <type> --input -` (stdin) or `--input <file>`.
- `signal.inputs` includes a `type` key. If it disagrees with `--type`, create fails with
  validation_error (exit 2).
- `--input` copies what the signal watches and its per-type settings (for example
  `lookBackTimeWindowInMonths`). It does not copy the name, the schedule, or the event filter.
  Without `--name`, the clone gets the type's default name; without `--schedule`, the type's
  default schedule; without `--filter`, the type's default filter.
- To copy those three, pass `--name`, `--schedule` (from `.schedule.periodUnit`), and `--filter`
  (from `.filter`) explicitly.
- A new signal is created Paused unless `--activate` is passed.
- 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)
  • jq
  • An existing audience segment for the people entity type (this doc uses audseg_0tm3lyrDBZzZgPaCsWx)

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. Create a source signal with non-default settings

clay signals create --type JobChange --name "TPC19 champions weekly" --schedule weekly \
  --filter f97.json \
  --input '{"entityType":"CONTACT","segmentIds":["audseg_0tm3lyrDBZzZgPaCsWx"],"lookBackTimeWindowInMonths":6}'

f97.json holds a filter requiring confidence >= 97 instead of the default 90. Real output, filtered with jq to the settings that matter here:

{"id":"td_0tmb2gerNf5UeWgg7Gu","name":"TPC19 champions weekly","sched":"weekly","filter":97,"lb":6}

2. Read what get returns for the inputs

clay signals get td_0tmb2fgbeg6dVh73MwS | jq -c '.signal.inputs'

This was run on a separate signal created with default settings. Real output:

{"type":"JobChange","lookBackTimeWindowInMonths":3,"entityType":"CONTACT","segmentIds":["audseg_0tm3lyrDBZzZgPaCsWx"]}

The object includes type and the defaulted lookBackTimeWindowInMonths, in addition to the two keys that were submitted.

3. Clone with --input alone

clay signals get td_0tmb2gerNf5UeWgg7Gu > src.json
jq -c .signal.inputs src.json | clay signals create --type JobChange --input -

Real output, filtered to name, schedule, filter threshold, and look-back:

{"id":"td_0tmb2gfJi7opmgxwbYF","name":"Event: Job change","sched":"monthly","filter":90,"lb":6}

The targeting and the look-back of 6 months carried over. The name reverted to the type's default (Event: Job change), the schedule to monthly, and the filter threshold to 90.

4. Clone with the name, schedule, and filter copied

jq -c .signal.inputs src.json | clay signals create \
  --type "$(jq -r .signal.type src.json)" \
  --name "$(jq -r .name src.json) (copy)" \
  --schedule "$(jq -r .schedule.periodUnit src.json)" \
  --filter "$(jq -c .filter src.json)" \
  --input -

Real output:

{"id":"td_0tmb2ggHTVHVz8GUrkY","name":"TPC19 champions weekly (copy)","sched":"weekly","filter":97,"lb":6,"runStatus":"Paused"}

5. Cloning with a mismatched type

clay signals get td_0tmb2fgbeg6dVh73MwS | jq -c .signal.inputs | clay signals create --type Promotion --input -

Real output (stderr, exit 2):

{"error":{"code":"validation_error","message":"--input: type is \"JobChange\", which disagrees with the signal type Promotion"}}

6. Confirm every schedule value round-trips

.schedule.periodUnit is accepted by --schedule for all five cadences. A signal created with each of --schedule daily|weekly|biweekly|monthly|quarterly stores:

{"periodAmount":1,"periodUnit":"daily"}
{"periodAmount":1,"periodUnit":"weekly"}
{"periodAmount":2,"periodUnit":"biweekly"}
{"periodAmount":1,"periodUnit":"monthly"}
{"periodAmount":1,"periodUnit":"quarterly"}

biweekly is stored with periodAmount: 2 and periodUnit: "biweekly", so copying periodUnit alone preserves it.

Verify the result

Confirm the full clone matches its source on name stem, schedule, filter, and look-back:

diff <(jq -S '{sched: .schedule.periodUnit, filter, lb: .signal.inputs.lookBackTimeWindowInMonths, seg: .input}' src.json) \
     <(clay signals get td_0tmb2ggHTVHVz8GUrkY | jq -S '{sched: .schedule.periodUnit, filter, lb: .signal.inputs.lookBackTimeWindowInMonths, seg: .input}') && echo identical

Expected: the command prints identical, because schedule, filter, look-back, and targeting match between source and clone.

How it works

signals get splits a signal's state across several top-level fields: signal.inputs holds what it watches and its per-type settings, while name, schedule, and filter are separate fields. signals create --input accepts the same shape signal.inputs has, which is what makes the pipe work, but it only receives that one field. The remaining three arrive through their own flags, and each falls back to the type's default when omitted.

Common issues

The clone has the default name and a 90 confidence threshold

Piping signal.inputs alone leaves --name, --schedule, and --filter unset, so the new signal gets the type's defaults for each. Pass all three from the source when an exact copy is the goal.

Stdin and file input are both supported

--input - reads stdin and --input <path> reads a file. The result is the same for both.

A clone is created Paused

--activate is not copied from anything, so the clone is Paused and does not run or spend until it is resumed. Cloning from an Active source was not tested.

Next steps

  • Use clay signals update to adjust the clone's schedule or filter afterward.
  • See the reconcile doc in this batch to manage a whole set of signals from a manifest instead of cloning one at a time.

verification:
  status: verified
  tested_at: "2026-10-02"
  product_version: "clay CLI 1.8.0+71cb1bf09c7e"
  command: "diff <(jq -S '{sched: .schedule.periodUnit, filter, lb: .signal.inputs.lookBackTimeWindowInMonths, seg: .input}' src.json) <(clay signals get <clone-id> | jq -S '{sched: .schedule.periodUnit, filter, lb: .signal.inputs.lookBackTimeWindowInMonths, seg: .input}') && echo identical"
  expected_result: "The command prints identical, because schedule, filter, look-back, and targeting match between source and clone."

Related Articles