Weezly Outreach
|Developer Docs

Campaigns API

Create, manage, and monitor outreach campaigns. Launch campaigns, track execution progress, and retrieve performance stats.

Campaign statuses: DRAFT ACTIVE PAUSED STOPPED COMPLETED ARCHIVED

List Campaigns

GET/api/campaigns

List campaigns with cursor-based pagination. Supports filtering by status, folder, and search. Each campaign includes a stats object with performance counts and rates.

Query Parameters

ParameterTypeDescription
statusstringFilter by status (DRAFT, ACTIVE, etc.)
searchstringSearch by campaign name
folderIdstringFilter by folder ID, or "none" for unfiled campaigns
cursorstringCursor for next page (from previous response)
limitnumberResults per page (default: 20, max: 50)
Request — cURLcURL
curl --location --request GET 'https://outreach.weezly.com/api/campaigns' \
  --header 'X-API-KEY: sk_live_YOUR_API_KEY'
Response - 200 OKJSON
{
  "campaigns": [
    {
      "id": "cm3camp456",
      "name": "Q3 SaaS Founders Outreach",
      "status": "ACTIVE",
      "folderId": null,
      "folder": null,
      "totalLeads": 150,
      "leadsCompleted": 65,
      "leadsReplied": 12,
      "leadsFailed": 3,
      "leadList": {
        "id": "cm3list789",
        "name": "SaaS Founders (West Coast)",
        "leadCount": 150
      },
      "senders": [
        { "id": "cs1", "account": { "id": "acc1", "linkedinName": "John Smith", "linkedinPhotoUrl": "https://..." } }
      ],
      "stats": {
        "connectionsSent": 150,
        "connectionsAccepted": 45,
        "messagesSent": 45,
        "inmailsSent": 0,
        "acceptanceRate": 30,
        "replyRate": 27
      },
      "createdAt": "2026-08-01T08:00:00.000Z",
      "updatedAt": "2026-08-18T14:30:00.000Z"
    }
  ],
  "nextCursor": "cm3camp789"
}

Create Campaign

POST/api/campaigns

Create a new campaign in DRAFT status. You must add steps and senders before launching.

Request Body

ParameterTypeDescription
namerequiredstringCampaign name
leadListIdrequiredstringID of the lead list to use
excludeListIdsstring[]Lead list IDs to exclude
excludeOptionsobjectAdditional exclusion rules (e.g. leads already in other active campaigns)
Request — cURLcURL
curl --location --request POST 'https://outreach.weezly.com/api/campaigns' \
  --header 'X-API-KEY: sk_live_YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{
  "name": "Q3 Enterprise Outreach",
  "leadListId": "cm3list789abc",
  "excludeListIds": [
    "cm3list_exclude1"
  ]
}'
Response - 201 CreatedJSON
{
  "campaign": {
    "id": "cm3camp_new789",
    "name": "Q3 Enterprise Outreach",
    "status": "DRAFT",
    "leadListId": "cm3list789abc",
    "leadList": { "id": "cm3list789abc", "name": "Enterprise CTOs", "leadCount": 89 },
    "totalLeads": 0,
    "createdAt": "2026-08-18T10:00:00.000Z"
  }
}

Response Codes

201Campaign created
400Name and leadListId required
404Lead list not found
401Unauthorized

Get Campaign

GET/api/campaigns/:id

Get full campaign details including stats, steps, and senders. nextActionAt is the soonest upcoming action on an ACTIVE campaign; processingNow is true while a lead is being worked on this instant.

Request — cURLcURL
curl --location --request GET 'https://outreach.weezly.com/api/campaigns/:id' \
  --header 'X-API-KEY: sk_live_YOUR_API_KEY'
Response - 200 OKJSON
{
  "campaign": {
    "id": "cm3camp456",
    "name": "Q3 SaaS Founders Outreach",
    "status": "ACTIVE",
    "prospectValue": 500,
    "smartReplyMode": "off",
    "timezone": "America/Los_Angeles",
    "workingHoursStart": 540,
    "workingHoursEnd": 1020,
    "workingDays": [1, 2, 3, 4, 5],
    "leadList": { "id": "cm3list789", "name": "SaaS Founders", "leadCount": 150 },
    "steps": [
      { "id": "step1", "type": "CONNECTION_REQUEST", "order": 1, "delayDays": 0 },
      { "id": "step2", "type": "MESSAGE", "order": 2, "delayDays": 3 }
    ],
    "senders": [
      { "id": "cs1", "account": { "id": "acc1", "linkedinName": "John Smith" } }
    ],
    "totalLeads": 150,
    "totalExecutions": 150,
    "leadsProcessed": 87,
    "leadsCompleted": 65,
    "leadsReplied": 12,
    "leadsFailed": 3,
    "leadsPending": 63,
    "leadsInProgress": 7,
    "leadsNotStarted": 63,
    "leadsExcluded": 0,
    "leadsStopped": 0,
    "nextActionAt": "2026-08-19T16:30:00.000Z",
    "processingNow": false,
    "connectionsSent": 150,
    "connectionsAccepted": 45,
    "messagesSent": 45,
    "inmailsSent": 0,
    "createdAt": "2026-08-01T08:00:00.000Z"
  }
}

Update Campaign

PATCH/api/campaigns/:id

Update campaign settings. name, folderId, prospectValue, and smartReplyMode can be changed in ANY status. Targeting and schedule fields (leadListId, excludeListIds, excludeOptions, timezone, working hours/days, start/end dates) require DRAFT or PAUSED status.

Request Body

ParameterTypeDescription
namestringCampaign name (any status; must not be empty)
folderIdstring|nullMove to folder, null to unfile (any status)
prospectValuenumberDollar value per replied lead, used for opportunity tracking (any status; must be >= 0)
smartReplyModestring"off" or "continue_on_ack" (any status). With continue_on_ack, a reply the AI classifies as a pure acknowledgment (e.g. "thanks for connecting") does not stop the sequence; each lead gets one such free pass, a second reply always stops.
leadListIdstringChange the source lead list (DRAFT or PAUSED only; must be one of your lists)
excludeListIdsstring[]Lead list IDs to exclude (DRAFT or PAUSED only)
excludeOptionsobjectAdditional exclusion rules (DRAFT or PAUSED only)
timezonestringIANA timezone, e.g. "America/New_York" (DRAFT or PAUSED only)
workingHoursStartnumberStart time in minutes from midnight, e.g. 540 = 9:00 AM (DRAFT or PAUSED only)
workingHoursEndnumberEnd time in minutes from midnight, e.g. 1020 = 5:00 PM (DRAFT or PAUSED only)
workingDaysnumber[]Working days, 0 = Sunday through 6 = Saturday (DRAFT or PAUSED only)
startDatestring|nullISO date the campaign may start sending (DRAFT or PAUSED only)
endDatestring|nullISO date the campaign stops sending (DRAFT or PAUSED only)
Request — cURLcURL
curl --location --request PATCH 'https://outreach.weezly.com/api/campaigns/:id' \
  --header 'X-API-KEY: sk_live_YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{
  "name": "Updated Campaign Name",
  "prospectValue": 500,
  "smartReplyMode": "continue_on_ack"
}'
Response - 200 OKJSON
{
  "campaign": {
    "id": "cm3camp456",
    "name": "Updated Campaign Name",
    "prospectValue": 500,
    "smartReplyMode": "continue_on_ack",
    "leadList": { "id": "cm3list789", "name": "SaaS Founders", "leadCount": 150 }
  }
}

Response Codes

200Campaign updated
400Empty name, invalid smartReplyMode, or targeting/schedule fields sent while not in DRAFT or PAUSED
404Campaign, lead list, or exclude list not found

Delete Campaign

DELETE/api/campaigns/:id

Delete a campaign. Only allowed for DRAFT, ARCHIVED, STOPPED, or COMPLETED campaigns.

Request — cURLcURL
curl --location --request DELETE 'https://outreach.weezly.com/api/campaigns/:id' \
  --header 'X-API-KEY: sk_live_YOUR_API_KEY'
Response - 200 OKJSON
{ "success": true }

Response Codes

200Campaign deleted
400Cannot delete active campaigns, stop first
404Campaign not found

Launch Campaign

POST/api/campaigns/:id/launch

Launch a DRAFT campaign. Requires at least one step and one sender; step content is validated (message text, videos, voice notes, branch setup). An EMPTY lead list is allowed: the campaign launches, stays ACTIVE, and enrolls leads automatically as they arrive in the list (API pushes, lead capture forms, imports).

Request — cURLcURL
curl --location --request POST 'https://outreach.weezly.com/api/campaigns/:id/launch' \
  --header 'X-API-KEY: sk_live_YOUR_API_KEY'
Response - 200 OKJSON
{
  "success": true,
  "status": "ACTIVE"
}

Response Codes

200Campaign launched
400Not in DRAFT status, no steps, no senders, or a step failed validation
404Campaign not found
502AI video template could not be created, launch aborted
503Background job service unavailable, campaign reverted to DRAFT

Stop Campaign

POST/api/campaigns/:id/stop

Stop an ACTIVE or PAUSED campaign. All pending executions are cancelled.

Request — cURLcURL
curl --location --request POST 'https://outreach.weezly.com/api/campaigns/:id/stop' \
  --header 'X-API-KEY: sk_live_YOUR_API_KEY'
Response - 200 OKJSON
{ "success": true, "status": "STOPPED" }

Response Codes

200Campaign stopped
400Only ACTIVE or PAUSED campaigns can be stopped
404Campaign not found

Pause Campaign

POST/api/campaigns/:id/pause

Pause an ACTIVE campaign. Executions are suspended but not cancelled.

Request — cURLcURL
curl --location --request POST 'https://outreach.weezly.com/api/campaigns/:id/pause' \
  --header 'X-API-KEY: sk_live_YOUR_API_KEY'
Response - 200 OKJSON
{ "success": true, "status": "PAUSED" }

Response Codes

200Campaign paused
400Only ACTIVE campaigns can be paused
404Campaign not found

Resume Campaign

POST/api/campaigns/:id/resume

Resume a PAUSED campaign. Untouched leads are re-planned onto the current schedule; leads mid-sequence keep their timing.

Request — cURLcURL
curl --location --request POST 'https://outreach.weezly.com/api/campaigns/:id/resume' \
  --header 'X-API-KEY: sk_live_YOUR_API_KEY'
Response - 200 OKJSON
{ "success": true, "status": "ACTIVE" }

Response Codes

200Campaign resumed
400Only PAUSED campaigns can be resumed
404Campaign not found

Duplicate Campaign

POST/api/campaigns/:id/duplicate

Duplicate a campaign as a new DRAFT. Copies all steps and settings with remapped IDs.

Request — cURLcURL
curl --location --request POST 'https://outreach.weezly.com/api/campaigns/:id/duplicate' \
  --header 'X-API-KEY: sk_live_YOUR_API_KEY'
Response - 200 OKJSON
{
  "success": true,
  "campaign": {
    "id": "cm3camp_dup123",
    "name": "Q3 SaaS Founders Outreach (copy)",
    "status": "DRAFT"
  }
}

Campaign Leads

GET/api/campaigns/:id/leads

Get leads in a campaign with their execution status, current step, and next step. Failed leads include a human-readable failureReason.

Query Parameters

ParameterTypeDescription
statusstringFilter by execution status, comma-separated: PENDING, ACTIVE, WAITING, COMPLETED, FAILED, STOPPED_REPLY, STOPPED. Also accepts ACCEPTED to filter to leads that accepted the connection request.
searchstringSearch by lead name, company, job title, or email
accountIdstringFilter by assigned sender account ID(s), comma-separated
stepTypestringFilter to leads that completed a step of the given type(s), comma-separated (e.g. CONNECTION_REQUEST,MESSAGE)
connectedstring"yes" = only 1st-degree connections, "no" = only leads not yet connected
pagenumberPage number (default: 1)
limitnumberResults per page (default: 25)
Request — cURLcURL
curl --location --request GET 'https://outreach.weezly.com/api/campaigns/:id/leads' \
  --header 'X-API-KEY: sk_live_YOUR_API_KEY'
Response - 200 OKJSON
{
  "executions": [
    {
      "id": "cm3exec789",
      "status": "WAITING",
      "currentStepOrder": 2,
      "nextRunAt": "2026-08-19T10:30:00.000Z",
      "lead": {
        "id": "cm3lead_001",
        "fullName": "Sarah Chen",
        "company": "TechStartup Inc.",
        "photoUrl": "https://...",
        "connectionStatus": "CONNECTED"
      },
      "assignedAccount": {
        "id": "acc1",
        "linkedinName": "John Smith"
      },
      "currentStepType": "MESSAGE",
      "nextStepType": "IF_CONNECTION",
      "failureReason": null
    }
  ],
  "total": 150,
  "page": 1,
  "totalPages": 6
}

Remove Lead from Campaign

DELETE/api/campaigns/:id/leads/:executionId

Remove a single lead from a campaign by its execution ID (from the Campaign Leads endpoint). Deletes the execution and its step history, and by default also removes the lead from the campaign's lead list so it is not automatically re-enrolled. Campaign counters are recomputed.

Query Parameters

ParameterTypeDescription
keepInListstringSet to 1 to keep the lead in the campaign's lead list (removes the execution only). Note: on an ACTIVE campaign the lead may then be re-enrolled by list sync.
Request — cURLcURL
curl --location --request DELETE 'https://outreach.weezly.com/api/campaigns/:id/leads/:executionId' \
  --header 'X-API-KEY: sk_live_YOUR_API_KEY'
Response - 200 OKJSON
{ "ok": true }

Response Codes

200Lead removed from the campaign
404Campaign not found, or lead not in this campaign
401Unauthorized

Campaign Stats

GET/api/campaigns/:id/stats

Get daily activity stats for a campaign.

Query Parameters

ParameterTypeDescription
daysnumberNumber of days of history (default: 7)
Request — cURLcURL
curl --location --request GET 'https://outreach.weezly.com/api/campaigns/:id/stats' \
  --header 'X-API-KEY: sk_live_YOUR_API_KEY'
Response - 200 OKJSON
{
  "daily": [
    { "date": "2026-08-15", "connections": 12, "messages": 5, "inmails": 0, "replies": 2 },
    { "date": "2026-08-16", "connections": 15, "messages": 8, "inmails": 0, "replies": 1 },
    { "date": "2026-08-17", "connections": 10, "messages": 12, "inmails": 2, "replies": 3 }
  ]
}

Campaign Steps

GET/api/campaigns/:id/steps

Get all campaign sequence steps with their configuration.

Request — cURLcURL
curl --location --request GET 'https://outreach.weezly.com/api/campaigns/:id/steps' \
  --header 'X-API-KEY: sk_live_YOUR_API_KEY'
Response - 200 OKJSON
{
  "steps": [
    {
      "id": "step1",
      "type": "CONNECTION_REQUEST",
      "order": 1,
      "branchId": null,
      "parentStepId": null,
      "delayDays": 0,
      "delayHours": 0,
      "inviteNote": "Hi {{firstName}}, I noticed we're both in the SaaS space...",
      "withdrawAfterDays": 25
    },
    {
      "id": "step2",
      "type": "IF_CONNECTION",
      "order": 2,
      "branchId": null,
      "parentStepId": "step1"
    },
    {
      "id": "step3",
      "type": "MESSAGE",
      "order": 3,
      "branchId": "step2_true",
      "parentStepId": "step2",
      "delayDays": 1,
      "messageText": "Great to connect, {{firstName}}! I wanted to share..."
    }
  ]
}

Update Campaign Steps

PUT/api/campaigns/:id/steps

Replace or edit the campaign's sequence steps. While the campaign is in DRAFT, the full sequence is replaced with the steps you send. Once a campaign has LEFT DRAFT (it was launched at least once), the sequence STRUCTURE is frozen to protect in-flight leads: you may still edit content (message text, invite notes, delays, media, per-step settings), but any change to the step count, IDs, types, order, or branching is rejected with 409 and code STRUCTURE_LOCKED. To restructure a started campaign, duplicate it instead.

Request Body

ParameterTypeDescription
stepsrequiredobject[]The full array of sequence steps. On a started campaign, every step must keep its existing id, type, order, branchId, and parentStepId.
Request — cURLcURL
curl --location --request PUT 'https://outreach.weezly.com/api/campaigns/:id/steps' \
  --header 'X-API-KEY: sk_live_YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{
  "steps": [
    {
      "id": "step1",
      "type": "CONNECTION_REQUEST",
      "order": 1,
      "inviteNote": "Hi {{firstName}}, updated note...",
      "withdrawAfterDays": 25
    }
  ]
}'
Response - 200 OKJSON
{
  "steps": [
    { "id": "step1", "type": "CONNECTION_REQUEST", "order": 1, "inviteNote": "Hi {{firstName}}, updated note..." }
  ]
}
Response - 409 Conflict (structure frozen)JSON
{
  "error": "Changing step type, order, or branching isn't allowed once a campaign has started. You can edit message content, delays, and settings. To restructure the sequence, duplicate the campaign.",
  "code": "STRUCTURE_LOCKED"
}

Response Codes

200Steps saved
400steps must be an array
404Campaign not found
409STRUCTURE_LOCKED: structural change attempted on a started campaign

Campaign Folders

Organize campaigns into folders. Move a campaign into a folder with PATCH /api/campaigns/:id and the folderId field (allowed in any campaign status). Deleting a folder never deletes its campaigns, they just become unfiled.

List Folders

GET/api/campaign-folders

Retrieve all campaign folders for the authenticated user, in display order.

Request — cURLcURL
curl --location --request GET 'https://outreach.weezly.com/api/campaign-folders' \
  --header 'X-API-KEY: sk_live_YOUR_API_KEY'
Response - 200 OKJSON
{
  "folders": [
    {
      "id": "cm3cfolder123",
      "name": "Q3 Campaigns",
      "emoji": "🎯",
      "position": 0,
      "_count": { "campaigns": 3 }
    }
  ]
}

Response Codes

200Folders returned
401Unauthorized

Create Folder

POST/api/campaign-folders

Create a new campaign folder. Folder names must be unique per user.

Request Body

ParameterTypeDescription
namerequiredstringFolder name
emojistringFolder emoji (defaults to 📁)
Request — cURLcURL
curl --location --request POST 'https://outreach.weezly.com/api/campaign-folders' \
  --header 'X-API-KEY: sk_live_YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{
  "name": "Q3 Campaigns",
  "emoji": "🎯"
}'
Response - 201 CreatedJSON
{
  "folder": {
    "id": "cm3cfolder123",
    "name": "Q3 Campaigns",
    "emoji": "🎯",
    "position": 0,
    "_count": { "campaigns": 0 }
  }
}

Response Codes

201Folder created
400Folder name is required
409A folder with this name already exists

Update Folder

PATCH/api/campaign-folders/:id

Rename a folder or change its emoji.

Request Body

ParameterTypeDescription
namestringNew folder name
emojistringNew folder emoji
Request — cURLcURL
curl --location --request PATCH 'https://outreach.weezly.com/api/campaign-folders/:id' \
  --header 'X-API-KEY: sk_live_YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{
  "name": "Q4 Campaigns"
}'
Response - 200 OKJSON
{ "success": true }

Response Codes

200Folder updated
404Folder not found
409A folder with this name already exists

Delete Folder

DELETE/api/campaign-folders/:id

Delete a folder. The campaigns inside it are not deleted, they become unfiled.

Request — cURLcURL
curl --location --request DELETE 'https://outreach.weezly.com/api/campaign-folders/:id' \
  --header 'X-API-KEY: sk_live_YOUR_API_KEY'
Response - 200 OKJSON
{ "success": true }

Response Codes

200Folder deleted
404Folder not found

Campaign Step Types

TypeDescription
CONNECTION_REQUESTSend a LinkedIn connection request with optional note
MESSAGESend a direct message to a connected lead
VIDEO_MESSAGESend a video message (recorded or AI-generated)
VOICE_NOTESend a voice note message
INMAILSend a LinkedIn InMail to a lead you are not connected with
VIEW_PROFILEView the lead's LinkedIn profile
REACT_TO_POSTReact to the lead's latest post (like, celebrate, support, ...)
COMMENT_ON_POSTAI-generated comment on the lead's latest post
IF_CONNECTIONBranch: check if the lead is a 1st-degree connection
IF_OPEN_PROFILEBranch: check if the lead's profile is an open profile
ENDEnd the sequence

Execution Statuses

StatusDescription
PENDINGNot yet started, waiting for its scheduled time
ACTIVECurrently being processed
WAITINGWaiting for the delay between steps
COMPLETEDAll steps finished successfully
FAILEDA step failed after retries
STOPPED_REPLYLead replied, the sequence stopped automatically
STOPPEDThe campaign was stopped before this lead finished