> ## Documentation Index
> Fetch the complete documentation index at: https://www.plungeai.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# plungeai-campaigns

> Run a list to completion on PlungeAI: the campaign ledger (claim/complete/fail/retry), the campaign-config (list source, cycle, cadence, retries, delivery), and how a campaign keeps going via cron or self-scheduling runAgain.


Run a list to completion on PlungeAI: the campaign ledger (claim/complete/fail/retry), the campaign-config (list source, cycle, cadence, retries, delivery), and how a campaign keeps going via cron or self-scheduling runAgain. A campaign is a saved workflow of kind `campaign`: create it over the One API (`POST /v1/workflows` with `kind: campaign` + `campaign_config`), MCP (`plungeai_workflow`) or Studio. Use when the user wants to process every row of a table on a cadence, run a batch job until a list is drained, or build a continuous monitor over a fixed set of items. Ledger status, pause and stop are Studio-session routes (`ocean-cli campaigns`); authoring UI: `plungeai-campaign-agent`; for the pipeline steps use `plungeai-workflows`.

[Download zip](https://skills.plungeai.com/plungeai-campaigns.zip) · [View raw SKILL.md](https://skills.plungeai.com/plungeai-campaigns/SKILL.md)

A campaign claims a list of items and works through them with per-item retries on
a repeating cadence, until the cycle is drained — the pattern behind "check every
row in this table weekly" or "watch these 50 URLs continuously." Its runtime is the
**campaign ledger**: a small set of `agent: data-table-agent` operations
(claim/complete/fail/release/status) that a CNL `type: batch` task with
`ledger: campaign` calls automatically.

## Prerequisites

- A self-service `ozk_` API key from **Dashboard → One API → Keys**
  (https://dashboard.plungeai.com) for REST (`https://api.plungeai.com`) and MCP
  (`https://mcp.plungeai.com/v1`), or build the pipeline in Ocean Studio.
- **A campaign is a saved workflow with `kind: campaign`.** REST, MCP and Studio create the same row; the
  ledger operations below are the engine's own (`data-table-agent` is an internal agent with no registry card —
  you do not discover or call it, you write it into the campaign YAML and the platform arms the ledger at save).

## Over the One API, MCP and CLI

| Job | REST (`ozk_` key) | MCP | CLI `ocean-cli …` |
|---|---|---|---|
| create | `POST /v1/workflows {name, kind: "campaign", yaml, campaign_config, folder?, description?}` → `201 {workflow, warnings?}` | `plungeai_workflow {action: "create", kind: "campaign", name, yaml, campaign_config}` | `workflows create` then `campaigns setup ID --config @campaign.json` (Studio session) |
| list, read | `GET /v1/workflows?kind=campaign` → `{workflows, total, limit, offset, has_more}`; `GET /v1/workflows/{id}` | `plungeai_list_workflows {kind: "campaign"}`; `plungeai_workflow {action: "get"}` | `workflows list --kind campaign` |
| change, delete | `PATCH /v1/workflows/{id}`; `DELETE /v1/workflows/{id}` → `204`; versions: `GET\|POST /v1/workflows/{id}/versions` | `plungeai_workflow` `update` / `delete` / `save_version` | `workflows update`, `delete`, `versions` |
| cadence | the campaign's job is a schedule: `GET /v1/schedules`, then `POST /v1/schedules/{id}/pause` \| `resume` \| `run` | `plungeai_schedule` `list` / `pause` / `resume` / `run_now` | `schedules …` |
| a campaign's runs | `GET /v1/executions?workflow_id=<campaign id>` | `plungeai_executions {action: "list"}` | `executions list` |
| ledger status, pause, stop | — (Studio session only) | — | `campaigns status \| pause \| resume \| stop \| complete ID` |

```bash
curl -s -X POST https://api.plungeai.com/v1/workflows \
  -H "Authorization: Bearer ozk_YOUR_KEY" -H "Content-Type: application/json" \
  -d "$(jq -n --rawfile yaml watch.yaml '{name: "Status-page watch", kind: "campaign", yaml: $yaml,
       campaign_config: {list: {inline: ["https://status.example-a.com", "https://status.example-b.com"]},
                         cycle: "continuous", schedule: "0 * * * *", max_attempts: 3}}')"
```

What builders get wrong:

- **`campaign_config` is JSON on REST and MCP** (the `campaign-config` fenced block is the Studio spelling of the same
  fields): `list` with exactly one of `table`, `agent`, `csv`, `inline`; `cycle`; `max_attempts` (integer ≥ 1) are
  required; `cycle_start`, `schedule` (5-field cron), `batch_size`, `max_items`, `pace`, `then`, `deliver`, `local` are
  optional. `422 invalid_params` names the field and nothing is created; `kind` other than `campaign` with a
  `campaign_config` is `422` too. The Studio-only `task_types` and `learn` keys are dropped.
- **A `201` can still carry `warnings`**: `campaign setup did not complete — the workflow is saved without its
  ledger`, `Ledger not created: …`, or a rejected `schedule`. Read them; the row exists either way.
- **No `schedule` means no scheduler job.** The ids of the ledger and the job are not in the `201`: find the job with
  `GET /v1/schedules` (named `<name> - Campaign`). For a campaign you build over the API, set `schedule`.
- **The save is linted** (`422 invalid_workflow`, `errors` listed): exactly ONE `agent: data-table-agent`,
  `operation: campaignClaim` task and ONE `type: batch` task with `items_from` and `ledger: campaign`; `concurrency` ≤
  5; no `ask_user` or `platform_action` inside the batch (it is unattended); an inner `type: harness` task lists
  `task_complete` in `allowed_tools`; `max_items` is required when an inner task can reach `data-table-agent`.
- **`DELETE` is a teardown, not a cascade**: the campaign's jobs go and its ledger header is tombstoned, results stay.
  The same teardown runs when a `PATCH` moves `kind` away from `campaign`.
- Every route is yours only: another account's campaign id is `workflow_not_found`, like a missing one.

## Discover the pieces

The items a campaign processes still come from ordinary discovery — never guess an
agent id for the per-item work: `plungeai_list_agents {search: "<what each item
needs done>"}` (REST: `GET /v1/discovery/search?kind=agents&q=…`). The ledger agent
`data-table-agent` is the exception: it is internal to the engine, has no discoverable card, and is
named only in the claim task of the campaign YAML.

## The shape

1. **`campaignClaim`** — a `type: task` leases up to `batch_size` pending items for
   this run.
2. **`type: batch` with `ledger: campaign`** — fans out over the claimed items
   (`items_from: claim`, `concurrency: N`); each per-item task's success/failure is
   written back to the ledger automatically as `campaignComplete`/`campaignFail`.
3. **A campaign config** tells the platform the list source, cycle
   (`once`/`hourly`/`daily`/`weekly`/`monthly`/`continuous`), cron `schedule`,
   `max_attempts`, and optional delivery/chaining: the `campaign_config` JSON on REST and MCP, a
   `campaign-config` fenced block after the YAML in Studio.

```yaml
workflow:
  name: "Status-page watch"
  tasks:
    - type: task
      id: claim
      agent: data-table-agent
      operation: campaignClaim
      campaign_id: "<written at save>"
      batch_size: 50
    - type: batch
      id: work
      items_from: claim
      concurrency: 5
      ledger: campaign
      tasks:
        - type: task
          id: one
          agent: firecrawl-agent
          operation: scrape
          url: "{item.key}"
          formats: [markdown]
```

```campaign-config
list: { inline: ["https://status.example-a.com", "https://status.example-b.com"] }
cycle: continuous
cycle_start: reset
schedule: "0 * * * *"
max_attempts: 3
deliver: [{ channel: slack }]
```

## Keeping it going

A campaign with a `schedule` in its config runs on an ordinary recurring cron job.
One WITHOUT a schedule ends its pipeline with `agent: scheduler, operation:
runAgain, in: "1h"` — a self-scheduling `@once` job that never stacks. Both
mechanisms and full cron/date-token detail: `plungeai-scheduling`.

## Gotchas

- **`upsertRow` is insert-only, not keyed** — it does not match `matchFields`; use
  `chainStep` or explicit `col_<name>` columns for an idempotent per-item write, or
  `updateRows` to change an existing row.
- Never chain (`then`) a dependent *analysis* report onto a data-collection
  campaign — a partial cycle must not trigger it; keep such reports time-triggered.
- The scheduler's zero-progress breaker auto-pauses a campaign after several
  consecutive runs that complete nothing — check `campaignStatus` before assuming
  it needs a manual nudge.
- Every ledger operation is scoped to the caller's own account — there is no
  cross-tenant campaign surface.

## Related skills

- `plungeai-campaign-agent` — the Studio kind that authors campaigns visually
  (kept id — this skill documents its underlying mechanics, not a replacement UI).
- `plungeai-workflows` — the `batch`/`task`/`harness` types a campaign pipeline is
  built from.
- `plungeai-scheduling` — cron cadence, `@once`, and `runAgain` self-scheduling.
- `plungeai-missions` — when a per-item step needs judgment instead of a fixed call.
- `plungeai-results-traces` — reading a campaign run's execution history.

## Reference

- [`references/campaigns.md`](/skills/plungeai-campaigns/references/campaigns) — the `campaign-config` field table, every ledger
  operation (`campaignBegin`/`Claim`/`Complete`/`Fail`/`Release`/`Status`/
  `AddItems`/`RetryFailed`/`SetStatus`/`Delete`/task rows/`chainStep`/`counterStep`),
  sizing, delivery channels, and gotchas.

## Reference pages

<CardGroup cols={2}>
<Card title="Campaigns — running a list to completion (the campaign ledger)" icon="file-text" href="/skills/plungeai-campaigns/references/campaigns">
A campaign claims a list of items (a data-table query, an agent's lister output, an uploaded CSV, or an inline list), runs each item through a CNL pipeline…
</Card>
</CardGroup>
