---
title: "Create Campaign"
method: POST
path: "/api/v1/campaigns"
tags: ["campaigns"]
---

# Create Campaign

`POST /api/v1/campaigns`

## Request

- Base URL: `https://api.nudgelink.co` (the document declares no server; this is the origin it was published from)
- URL: `https://api.nudgelink.co/api/v1/campaigns`
- Auth: HTTP bearer

## Request body

- CampaignCreate
  - `name` string, required
  - `instruction_note` string, nullable
  - `icp_override` object, nullable
  - `account_ids` string[], nullable
  - `outreach_objective` 'adaptive' | 'conversation' | 'problem_discovery' | 'offer': The job the FIRST substantive message should accomplish (Outreach Objective Layer spec, 2026-07-20). A layer ABOVE the Voice Engine — it controls the message's goal and CTA, never the voice. ``adaptive`` = NudgeLink resolves the concrete objective per lead from fit (relevance_score), intent (intent_score) and signals; the other three are the resolvable concrete objectives. Deliberately NOT a 1/2/3 scale — these are different jobs, not different aggressiveness levels. Member names equal their values (house convention) so SAEnum name/value storage agrees.
  - `style_profile` object, nullable
  - `languages` string[], nullable

## Response `201`

Successful Response

- CampaignOut
  - `id` string, uuid, required
  - `name` string, required
  - `status` string, required
  - `instruction_note` string, nullable, required
  - `icp_override` object, nullable, required
  - `account_ids` string[], nullable, required
  - `created_at` string, date-time, required
  - `stats` CampaignStats, required
    - `leads_total` integer, required
    - `leads_by_status` object, required
    - `active_sequences` integer, required
    - `replied` integer, required
    - `startable` integer
    - `pending_approvals` integer
    - `outreach` OutreachStats
      - `invites` InviteStats: Connection requests. ``*_pct`` are None (never 0.0) on an empty base. THREE outcomes, not two (added 2026-08-07 after the first production backfill). An invite we sent is in exactly one of: * **accepted** — ``invite_accepted_at`` is stamped. * **declined** — we looked AFTER inviting and they are still not a connection. "Declined" is shorthand: it also covers ignored. * **unknown** — we have no observation since the invite went out. That third bucket is not a technicality. Relationship distance is only refreshed by the DM gate, which runs when a sequence reaches ``linkedin_followup_1``; ``warmup_view``'s blob is written BEFORE the invite and carries no timestamp at all. So a lead whose follow-up is still sitting in the preview queue has never been re-checked, and folding those into the denominator reports them as rejections. The first prod run showed exactly that: 379 invites, 376 "not 1st-degree", an 0.8% accept rate. Real cold-invite accept rates are 15-40%. Nearly all of that 376 was "we never looked", and dividing by it would have put a catastrophic-looking number in front of clients. So ``accept_rate_pct`` divides by **accepted + declined** — what we actually know — and ``unknown`` is reported alongside so the number can be read with its own confidence attached. The bucket shrinks toward zero once PR 2's ``relation.new`` webhook lands, which is the real fix.
        - `sent` integer
        - `with_note` integer
        - `without_note` integer
        - `accepted` integer
        - `accepted_with_note` integer
        - `accepted_without_note` integer
        - `declined` integer
        - `unknown` integer
        - `judged` integer
        - `judged_with_note` integer
        - `judged_without_note` integer
        - `accept_rate_pct` number, nullable
        - `accept_rate_with_note_pct` number, nullable
        - `accept_rate_without_note_pct` number, nullable
      - `replies` ReplyStats: Messages sent and what came back. ``contacted`` counts SEQUENCES (people) reached by any substantive step, not messages — an invite plus two follow-ups to one lead is one person. ``silent`` is the complement, so positive + neutral + negative + unclassified + silent == contacted.
        - `dms_sent` integer
        - `emails_sent` integer
        - `contacted` integer
        - `replied` integer
        - `reply_rate_pct` number, nullable
        - `positive` integer
        - `neutral` integer
        - `negative` integer
        - `unclassified` integer
        - `silent` integer
      - `comments_sent` integer
  - `outreach_objective` 'adaptive' | 'conversation' | 'problem_discovery' | 'offer', required: The job the FIRST substantive message should accomplish (Outreach Objective Layer spec, 2026-07-20). A layer ABOVE the Voice Engine — it controls the message's goal and CTA, never the voice. ``adaptive`` = NudgeLink resolves the concrete objective per lead from fit (relevance_score), intent (intent_score) and signals; the other three are the resolvable concrete objectives. Deliberately NOT a 1/2/3 scale — these are different jobs, not different aggressiveness levels. Member names equal their values (house convention) so SAEnum name/value storage agrees.
  - `resolved_outreach_objective` 'adaptive' | 'conversation' | 'problem_discovery' | 'offer', required: The job the FIRST substantive message should accomplish (Outreach Objective Layer spec, 2026-07-20). A layer ABOVE the Voice Engine — it controls the message's goal and CTA, never the voice. ``adaptive`` = NudgeLink resolves the concrete objective per lead from fit (relevance_score), intent (intent_score) and signals; the other three are the resolvable concrete objectives. Deliberately NOT a 1/2/3 scale — these are different jobs, not different aggressiveness levels. Member names equal their values (house convention) so SAEnum name/value storage agrees.
  - `outreach_objective_source` string, required
  - `style_profile` object, nullable, required
  - `languages` string[], nullable, required
  - `email_mode` string
  - `mailbox_id` string, uuid, nullable

## Other responses

- `422`: Validation Error

---

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