---
title: "Create an outreach campaign"
method: POST
path: "/api/v1/campaigns"
---

# Create an outreach campaign

`POST /api/v1/campaigns`

## Request

- Base URL: `https://www.easeclaw.com`
- URL: `https://www.easeclaw.com/api/v1/campaigns`
- Auth: HTTP bearer

Creates a campaign against a project the caller owns: who it reaches, what the invite note and first message say, and whether every batch waits for the owner (copilot) or the campaign runs inside the limits they set (autopilot). Creating a campaign sends nothing. People it later collects wait in a review queue. Rate-limited to 20/hour per account, shared with the dashboard. Requires an outreach-scope API key (Pro plan or higher).

## Request body

- object
  - `project_id` string, uuid, required: The project this campaign belongs to
  - `name` string, required
  - `source_type` 'competitor_harvest' | 'signal' | 'profile_urls' | 'csv' | 'basic_search' | 'sales_navigator' | 'recruiter' | 'my_network': Where a campaign's people come from. Defaults to competitor_harvest.
  - `source_url` string: competitor_harvest: the LinkedIn company page, profile or post to mine. sales_navigator/recruiter: the saved search URL, stored byte for byte.
  - `source_params` object: basic_search only: the search filters to re-run. Required for that kind.
  - `signal_min_score` integer: signal campaigns: the intent bar a person with an intent signal must clear to be fed in; engagers are admitted on ICP fit instead
  - `icp` object: This campaign's own targeting. Omit to inherit the project's ICP unchanged.
  - `mode` 'autopilot' | 'copilot': Who approves what goes out: autopilot: the agent approves inside the owner's limits; copilot: every batch waits for the owner. Neither mode is reachable from this API: it decides who approves, and this surface is not either of them.
  - `invite_note_mode` 'none' | 'custom'
  - `invite_note_template` string
  - `first_message_template` string: The post-acceptance first message. {{first_name}} and {{meeting_link}} substitute at approval.
  - `meeting_link` string, uri: https booking link that {{meeting_link}} substitutes
  - `accept_wait_days` integer
  - `daily_quota` integer: Cap on how many of this campaign's people move per day

## Response `201`

The created campaign

- object
  - `campaign` Campaign
    - `id` string, uuid
    - `project_id` string, uuid
    - `name` string
    - `source_type` 'competitor_harvest' | 'signal' | 'profile_urls' | 'csv' | 'basic_search' | 'sales_navigator' | 'recruiter' | 'my_network': Where a campaign's people come from. Defaults to competitor_harvest.
    - `source_ref` string, nullable: What this campaign sources from, per source_type. Null for signal campaigns (their source is the project) and for the multi-ref kinds, where provenance is recorded per person instead.
    - `source_params` object, nullable: basic_search only: the search descriptor it re-runs from
    - `signal_min_score` integer, nullable
    - `icp` object, nullable: This campaign's own targeting; null means it inherits the project's ICP
    - `status` 'draft' | 'sourcing' | 'review' | 'active' | 'paused' | 'done' | 'stopped'
    - `mode` 'autopilot' | 'copilot': Who approves what goes out: autopilot: the agent approves inside the owner's limits; copilot: every batch waits for the owner. Neither mode is reachable from this API: it decides who approves, and this surface is not either of them.
    - `invite_note_mode` 'none' | 'custom'
    - `invite_note_template` string, nullable
    - `first_message_template` string, nullable
    - `accept_wait_days` integer
    - `followup_wait_days` integer
    - `withdraw_after_days` integer, nullable: null = auto-withdraw off
    - `email_handoff_enabled` boolean
    - `email_handoff_campaign_id` string, nullable
    - `meeting_link` string, uri, nullable
    - `goal` 'meeting' | 'reply' | 'share_link' | 'null', nullable
    - `tone` 'friendly' | 'professional' | 'direct' | 'casual' | 'null', nullable
    - `language` 'en-US' | 'en-GB' | 'fr' | 'de' | 'es' | 'nl' | 'null', nullable
    - `exclude_first_degree` boolean
    - `split_messages` boolean
    - `email_fallback_days` integer, nullable: null = email fallback off
    - `daily_quota` integer, nullable
    - `created_at` string, date-time
    - `updated_at` string, date-time
  - `source` object, nullable: How the campaign's source was parsed, when it has one

## Other responses

- `400`: Error
- `401`: Error
- `402`: Error
- `403`: The key is not outreach-scoped (code outreach_scope_required). Campaign paths need a key minted with the outreach scope at https://www.easeclaw.com/leads/api. The scope cannot be widened on an existing key.
- `429`: Rate limited (300/hour per account; drafting also 30/hour + daily AI budget). Retry-After header set when known.
- `500`: Error

---

[API](https://skmtc.dev/easeclaw/apis/easeclaw-api.md) · [All operations](https://skmtc.dev/easeclaw/apis/easeclaw-api/llms.txt) · [OpenAPI document](https://skmtc.dev/easeclaw/apis/easeclaw-api/revisions/79a190b20f61?raw)
