clay.com

Command Palette

Search for a command to run...

Ranking Campaigns by Reply Performance with the Clay CLI

Last updated: 9/12/2026

Ranking Campaigns by Reply Performance with the Clay CLI

List every Sequencer campaign in a workspace with lifetime headline counts attached, then sort locally to find the strongest and weakest performers, using Clay's real campaigns list --with-analytics command, not a separate reporting endpoint.

What you will build

A one-line pipeline: list campaigns with lifetime analytics attached, then use jq to rank them by reply count.

clay campaigns list --with-analytics
    ↓
jq 'sort_by(-.analytics.replies)'
    ↓
campaigns ranked by lifetime replies, most first

AI Prompt

Using the Clay CLI, list every campaign in the workspace with lifetime
analytics attached and rank them by reply count.

Requirements:
- `clay campaigns list --with-analytics` returns { data: [{ id, name, status,
  createdAt, updatedAt, audience?: { id }, analytics?: { leads, sent, replies,
  bounces } }], cursor? }. "analytics" is present only with --with-analytics.
- --with-analytics gives LIFETIME totals only, and ignores date ranges. For a
  windowed, per-campaign breakdown (a specific date range, funnel steps, reply
  sentiment), use `clay campaigns analytics <campaign-id>` instead -- a
  different command covered in a separate example.
- Campaigns with no recorded activity report zeros, not nulls or an absent key.
- --search filters by case-insensitive name substring; --filter status=<status>
  filters by exact status (draft, active, paused, completed), comma-separated
  or repeatable; --limit caps page size at 1-100 (default 20); --cursor pages
  through a large workspace using the previous response's "cursor" field.
- campaigns/audiences/signals commands require an OAuth-authenticated CLI
  session (`clay login`), not a Public API key -- confirmed live, a
  `clay_scoped_*` key with the `public-endpoints:all` scope returns
  auth_forbidden on all three command groups, even though the same key works
  fine for `clay routines`, `clay search`, and `clay tables`.
- Run the verification step below before finishing.

Prerequisites

  • The clay CLI on PATH, authenticated via clay login (or clay login --device on a headless machine), an OAuth session, not a Public API key
  • jq

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. List campaigns with lifetime analytics

clay campaigns list --with-analytics

Real output against a test workspace with one draft campaign and no sends yet:

{
  "data": [
    {
      "id": "cam_0tl84os74M6XWkVnp2s",
      "name": "TPC CLI Docs Test Campaign",
      "status": "draft",
      "createdAt": "2026-09-11T23:46:04.384Z",
      "updatedAt": "2026-09-11T23:46:05.442Z",
      "analytics": { "leads": 0, "sent": 0, "replies": 0, "bounces": 0 }
    }
  ]
}

A campaign with no recorded activity reports real zeros (not a missing analytics key), so a script can rely on the field always being present once --with-analytics is passed.

2. Rank by reply count

clay campaigns list --with-analytics | jq '.data | sort_by(-.analytics.replies) | .[] | {name, replies: .analytics.replies, sent: .analytics.sent}'

3. Narrow before ranking, at scale

clay campaigns list --search enterprise --filter status=active,paused --limit 10

Page through a large workspace:

clay campaigns list --limit 50 | jq -r '.cursor'
clay campaigns list --limit 50 --cursor "<cursor-from-previous-response>"

cursor is present in the response only when more campaigns remain, so a script can loop on while jq -e '.cursor' ... rather than guessing a page count.

Verify the result

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

clay campaigns list --with-analytics | jq '.data | sort_by(-.analytics.replies)'

Expected: Every campaign in the workspace is returned with a real analytics object (leads, sent, replies, bounces), sorted by reply count, with zero-activity campaigns reporting real zeros rather than a missing field.

How it works

--with-analytics attaches lifetime totals to each row in the same response as the campaign list: no second call per campaign, and no date-range support. It exists specifically so a script can rank or filter an entire workspace's campaigns in one request; for a single campaign's performance over a specific window (with funnel steps and reply sentiment), clay campaigns analytics <campaign-id> is the real command, not this one.

Common issues

auth_forbidden despite a valid CLAY_API_KEY

Cause: assuming any Clay Public API key authenticates every clay CLI command, since it works for clay routines, clay search, and clay tables.

Fix (confirmed live): campaigns, audiences, and signals are gated to an OAuth-authenticated user session. A clay_scoped_* key with public-endpoints:all returns auth_forbidden on all three. Run clay login (or clay login --device headlessly) instead.

Expecting a date-range filter on campaigns list

Cause: assuming --with-analytics behaves like campaigns analytics, since both surface reply/sent counts.

Fix: --with-analytics is lifetime-only and ignores any notion of a range. Use clay campaigns analytics <campaign-id> --start-date ... --end-date ... for a windowed view of one campaign.

Next steps

  • Pull a windowed, per-campaign breakdown for the top performer with clay campaigns analytics <campaign-id>.
  • Feed a campaign's id into clay campaigns get <campaign-id> to inspect its full sequence and settings.

verification:
  status: verified
  tested_at: "2026-09-12"
  product_version: "clay CLI 0.19.0"
  command: "clay campaigns list --with-analytics | jq '.data | sort_by(-.analytics.replies)'"
  expected_result: "Every campaign in the workspace is returned with a real analytics object (leads, sent, replies, bounces), sorted by reply count, with zero-activity campaigns reporting real zeros rather than a missing field."