Skip to main content
POST
/
api
/
campaigns
Create a campaign
curl --request POST \
  --url https://app.puffle.ai/api/campaigns \
  --header 'Authorization: Bearer <token>' \
  --header 'Content-Type: application/json' \
  --data '
{
  "type": "linkedin",
  "name": "<string>",
  "sender_account_ids": [
    "3c90c3cc-0d44-4b50-8888-8dd25736052a"
  ],
  "skip_other_campaigns": true,
  "operating_hours": {},
  "daily_limit": 250,
  "stop_on_reply": true,
  "open_tracking": true,
  "signature": "<string>"
}
'
{ "campaign": { "id": "8c2b2d4e-e29b-41d4-a716-446655440000", "user_id": "11111111-1111-1111-1111-111111111111", "type": "email", "name": "Q2 Enterprise Outreach", "status": "draft", "daily_limit": 50, "stop_on_reply": true, "open_tracking": true, "signature": "Best,\n%first_name%", "skip_other_campaigns": null, "operating_hours": null, "stats": null, "created_at": "2026-04-22T10:00:00Z", "updated_at": "2026-04-22T10:00:00Z", "sequence_nodes": [ { "id": "bbbb1111-1111-1111-1111-111111111111", "campaign_id": "8c2b2d4e-e29b-41d4-a716-446655440000", "position": 0, "type": "email", "subject": "", "body": "" }, { "id": "bbbb2222-1111-1111-1111-111111111111", "campaign_id": "8c2b2d4e-e29b-41d4-a716-446655440000", "position": 1, "type": "end" } ] } }

Overview

Creates a draft campaign for one channel (linkedin or email) and seeds it with a two-node default sequence — either [connection_request, end] or [email, end]. The campaign is not yet launchable — the email body, prospects, and any extra steps still need to be added.

Prerequisites

You need at least one connected sender account that matches type:

Typical follow-up sequence

  1. updateCampaign — fill in sequence_nodes (for email, set subject and body; add more steps if needed)
  2. POST /api/campaigns/{id}/leads — add prospects (direct insert, CSV upload, or import from a saved list)
  3. GET /api/campaigns/{id}/validate — pre-launch sanity check
  4. launchCampaign — start sending

AI agent notes

Name uniqueness (409). Names are unique per user — retry with a suffix ("Q2 Enterprise Outreach (2)") if you hit a 409.Channel-specific fields. LinkedIn-only fields (skip_other_campaigns, operating_hours) on an email campaign and email-only fields (daily_limit, stop_on_reply, open_tracking, signature) on a LinkedIn campaign are silently ignored — not rejected. Send only the fields that apply.Default sequence is intentionally empty. Email: subject and body are empty strings. You must populate them via updateCampaign (or use the AI framework chat → generate-messages flow) before launchCampaign will succeed.Sender accounts must match type. A LinkedIn account id on an email campaign (or vice versa) returns 400. Filter GET /api/accounts by type before picking.No prospects yet. Creation seeds the sequence but adds no prospects. launchCampaign on a prospect-less campaign exits immediately with status: "completed".

Authorizations

Authorization
string
header
required

Bearer authentication header of the form Bearer <token>, where <token> is your auth token.

Body

application/json

Fields are channel-specific. type, name, and sender_account_ids are required; the rest apply only to one channel and are silently ignored on the other.

type
enum<string>
required

Channel. Immutable after creation — you cannot convert a LinkedIn campaign to email later.

Available options:
linkedin,
email
name
string
required

Display name. Must be unique per user — duplicates return 409.

Minimum string length: 1
sender_account_ids
string<uuid>[]
required

One or more connected accounts (from GET /api/accounts?type=<type>) that match type. Must be owned by the caller; mismatched channel returns 400.

Minimum array length: 1
Pattern: ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$
skip_other_campaigns
boolean

LinkedIn only. If true (default), prospects already in another active LinkedIn campaign are skipped at launch. Ignored for email campaigns.

operating_hours
object

LinkedIn only. Per-weekday schedule (JSONB). Defaults to Mon–Fri 9–17 in the user's timezone if omitted. Ignored for email campaigns.

daily_limit
integer

Email only. Max prospects per day across all senders (clamped 1–500, defaults to 50). Ignored for LinkedIn campaigns.

Required range: 1 <= x <= 500
stop_on_reply
boolean

Email only. If true (default), a reply auto-terminates the prospect. Ignored for LinkedIn campaigns.

open_tracking
boolean

Email only. Insert a tracking pixel (default true). Ignored for LinkedIn campaigns.

signature
string

Email only. Template with %sender_*% and %first_name% variables. Defaults to "Best,\n%first_name%".

Response

Campaign created as a draft. Returns the campaign with its seeded default sequence nodes.

campaign
object
required