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)
productIdstringWeezly Product whose ICP the campaign scores leads against (IF_ICP_SCORE). Omit for the workspace default.
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.

Response Fields (selection)

ParameterTypeDescription
emailsSentnumberEmail steps sent. emailReplies, emailsSkipped and emailsBounced are filled only when the sequence has an EMAIL step (0 otherwise).
opportunitiesnumberLeads that became an opportunity: an AI-detected interested reply, or the lead entered a positive pipeline stage. Never decreases.
icpFilterednumberLeads stopped by an IF_ICP_SCORE gate
awaitingAcceptanceobject|nullInvites sent and waiting for acceptance: count, and until (the earliest withdraw deadline)
splitTestobject|nullA vs B results when the sequence has a SPLIT_TEST step: per variant leads, invitesSent, accepted, messagesSent, replied, opportunities, acceptRate, replyRate, plus winner ("A", "B" or null), confidence and enoughData (needs 50 leads per variant)
smartReplyModestring"off" or "continue_on_ack"
autoFindEmailsbooleanWhether emails are looked up automatically for leads without one (sequences with email steps)
leadLocalTimebooleanWhether message steps go out in each lead's local time
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",
    "autoFindEmails": true,
    "leadLocalTime": false,
    "productId": null,
    "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,
    "emailsSent": 0,
    "emailReplies": 0,
    "emailsSkipped": 0,
    "emailsBounced": 0,
    "icpFiltered": 0,
    "opportunities": 9,
    "awaitingAcceptance": { "count": 30, "until": "2026-09-12T10:00:00.000Z" },
    "splitTest": null,
    "createdAt": "2026-08-01T08:00:00.000Z"
  }
}

Update Campaign

PATCH/api/campaigns/:id

Update campaign settings. name, folderId, prospectValue, smartReplyMode, autoFindEmails, leadLocalTime and productId 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.
productIdstring|nullWeezly Product (sales profile + ICP) the campaign scores leads against (any status; forward-only). null = the workspace's default product. Products are managed in Weezly under Settings > Workspace > Products.
autoFindEmailsbooleanLook up emails automatically for leads without one when the sequence has email steps (any status). Lookups use email-finding credits.
leadLocalTimebooleanSend message, video, voice, InMail and email steps inside the working hours in each LEAD's local time instead of the campaign timezone (any status). Invites, profile views, likes and follows keep the campaign timezone.
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

Update Senders

PUT/api/campaigns/:id/senders

Set which LinkedIn accounts send the campaign and, for sequences with EMAIL steps, which mailboxes send its emails. Replaces the current list. Only allowed while the campaign is in DRAFT or PAUSED status. Mailboxes are their own pool: each lead is assigned one mailbox at its first email step and keeps it for follow-ups.

Request Body

ParameterTypeDescription
accountIdsrequiredstring[]LinkedIn account IDs (from GET /api/accounts). An empty array is accepted on a draft; launch still requires at least one sender.
emailAccountIdsstring[]Email account IDs. Omit to leave the email senders unchanged. A mailbox that is disconnected or over your seat limit cannot be added (one already on the campaign may stay). A PAUSED campaign with email steps must keep at least one.
Request — cURLcURL
curl --location --request PUT 'https://outreach.weezly.com/api/campaigns/:id/senders' \
  --header 'X-API-KEY: sk_live_YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{
  "accountIds": [
    "acc1",
    "acc2"
  ],
  "emailAccountIds": [
    "mbx1"
  ]
}'
Response - 200 OKJSON
{
  "senders": [
    { "id": "cs1", "accountId": "acc1", "account": { "id": "acc1", "linkedinName": "John Smith", "linkedinPhotoUrl": "https://..." } },
    { "id": "cs2", "accountId": "acc2", "account": { "id": "acc2", "linkedinName": "Anna Berg", "linkedinPhotoUrl": "https://..." } }
  ],
  "emailSenders": [
    { "id": "ces1", "emailAccountId": "mbx1", "emailAccount": { "id": "mbx1", "email": "john@acme.com", "provider": "GOOGLE", "status": "ACTIVE" } }
  ]
}

Response Codes

200Senders saved
400Not in DRAFT or PAUSED status, accountIds/emailAccountIds not an array, an account or mailbox not found, a new mailbox is disconnected or over the seat limit, or all mailboxes removed from a PAUSED campaign with email steps
404Campaign 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 usable sender; step content is validated (message text, videos, voice notes, email subjects, 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 Body (optional)

ParameterTypeDescription
findEmailsbooleanOverrides the campaign's autoFindEmails setting for sequences with email steps
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",
  "warnings": [
    "Finding emails for 12 lead(s) in the background; leads still without one follow the No-Email path or skip email steps."
  ]
}

warnings is present only when there is something to know (leads without an email, a large pending-invitation backlog on a sender). Warnings never block the launch. Every failure returns error with a readable message; some also carry a code:

Launch Failures (HTTP 400 unless noted)

ParameterTypeDescription
no code (setup)400Not in DRAFT status, no steps or no senders
end_date_passed400The campaign end date is already in the past
billing_hold400Every sender is suspended because the subscription payment failed. Includes suspendedSenders.
seat_limit400Every LinkedIn sender, or every email sender, is over your seat limit. Includes suspendedSenders and suspendedMailboxes.
no code (senders)400None of the selected senders are active (disconnected)
no code (email)400The sequence has email steps but email steps are not enabled for the workspace, no email sender is selected, or none of them is active
no code (steps)400A step failed validation: empty message or variation, unconfigured AI video or fallback, missing video or voice note, email without subject or body, subject variables without a fallback subject, steps only on a connection request's Not-Accepted branch, an empty A/B variant, or more than one split test
no code (AI video)502The AI video template could not be created, launch aborted
no code (service)503Background job service unavailable, campaign reverted to DRAFT

Response Codes

200Campaign launched
400Launch refused, see the table above
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
400Not PAUSED, no usable sender (code seat_limit or billing_hold, same as launch), an email sequence with no active email sender, or the end date has passed (code end_date_passed)
404Campaign not found

Restart Campaign

POST/api/campaigns/:id/restart

Restart a STOPPED or COMPLETED campaign. mode "fresh" (default) deletes every lead's progress and launches again from the first step; mode "resume" puts the leads that were stopped mid-sequence back where they were. In resume mode, stopped leads whose sender is no longer active are marked FAILED.

Request Body (optional)

ParameterTypeDescription
modestring"fresh" (default) or "resume"
Request — cURLcURL
curl --location --request POST 'https://outreach.weezly.com/api/campaigns/:id/restart' \
  --header 'X-API-KEY: sk_live_YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{
  "mode": "resume"
}'
Response - 200 OKJSON
{ "success": true, "status": "ACTIVE", "mode": "resume", "reactivated": 42 }

Response Codes

200Campaign restarted. reactivated (resume mode only) is the number of leads put back.
400Not STOPPED or COMPLETED, no usable sender (code seat_limit or billing_hold), the end date has passed (code end_date_passed), or in resume mode no stopped leads / no active sender for them
404Campaign not found
503Background job service unavailable (fresh mode)

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. Step order must be contiguous (1..N) within each branch; gaps are healed when a draft is saved. 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.

Step Fields

ParameterTypeDescription
idstringStep ID. Branch IDs are built from it, so send your own stable IDs when you create branches.
typerequiredstringOne of the step types listed under Campaign Step Types
orderrequirednumberPosition within its branch, 1..N with no gaps per branch
branchIdstring|nullnull for the main path. Steps after a fork use "<stepId>_true" or "<stepId>_false" of the forking step (condition steps, SPLIT_TEST: true = Variant A, false = Variant B, CONNECTION_REQUEST: accepted / not accepted, message steps: replied / no reply)
parentStepIdstring|nullThe forking step a branch belongs to
delayDays / delayHours / delayMinutesnumberWait before this step, measured from the previous step (randomized by about 20%). The step right after an IF_ICP_SCORE gate never waits.
inviteNotestringCONNECTION_REQUEST note (may use {{variables}})
withdrawAfterDaysnumberCONNECTION_REQUEST: withdraw the invite if not accepted after N days (0 = never)
messageTextstringText of MESSAGE, INMAIL and EMAIL steps (may use {{variables}})
fallbackMessagestringSent instead when a {{variable}} cannot be filled for the lead. Required when the text uses variables.
messageVariationsobject[]A/B message variations: [{ messageText, fallbackMessage }]. Replaces messageText when present.
emailSubjectstringEMAIL subject, also the optional InMail subject. On a follow-up EMAIL, empty = reply in the same thread.
emailSubjectFallbackstringSubject used when a subject variable cannot be filled (no variables allowed in it)
attachmentUrl / attachmentNamestringMESSAGE: file sent with the text in one message (image, PDF, Office document, TXT or CSV, max 20 MB)
videoUrlstringVIDEO_MESSAGE: recorded video URL
voiceUrlstringVOICE_NOTE: voice recording URL
reactionTypestringREACT_TO_POST: like, celebrate, support, love, insightful or funny
commentTonestringCOMMENT_ON_POST: supportive, professional, excited, curious, congratulatory or thoughtful
commentLengthstringCOMMENT_ON_POST: ultra_short, super_short, short, medium or long
commentEmojis / commentRulesboolean / string[]COMMENT_ON_POST: allow emojis, extra instructions for the AI
icpMinScorenumberIF_ICP_SCORE: minimum ICP score 1 to 10 to continue (6 = good fit and above)
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 an optional note (inviteNote). Branches: _true = accepted, _false = not accepted yet. Free LinkedIn plans always send without a note.
MESSAGESend a LinkedIn message to a connected lead, optionally with a file attachment. Branches: _true = replied, _false = no reply yet.
VIDEO_MESSAGESend a video message, either a recorded video (videoUrl) or an AI-generated personalized video
VOICE_NOTESend a voice note (voiceUrl)
INMAILSend an InMail to a lead you are not connected with, with an optional subject. A connected lead gets a regular message instead.
EMAILSend an email from one of the campaign's email senders (emailSubject + messageText). Requires email steps on your workspace.
VIEW_PROFILEView the lead's LinkedIn profile
LIKE_POSTLike the lead's latest post
REACT_TO_POSTReact to the lead's latest post with the chosen reactionType
COMMENT_ON_POSTPost an AI-written comment on the lead's latest post
FOLLOW_PROFILEFollow the lead's LinkedIn profile
IF_CONNECTIONCondition: _true = the lead is a 1st-degree connection, _false = not connected
IF_OPEN_PROFILECondition on the lead's open profile. Currently every lead takes the _false (not open) path.
IF_HAS_EMAILCondition: _true = the lead has a usable email (not bounced, not unsubscribed), _false = no email
IF_ICP_SCOREGate, not a fork: leads scoring at least icpMinScore (1 to 10) against your ICP continue on the same branch, the rest end. One gate per path.
SPLIT_TESTA/B split test: each lead is assigned once to Variant A (_true) or Variant B (_false), balanced per sender. One per campaign, both variants need steps.
ENDEnd the sequence for the lead

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
PAUSEDThe campaign is paused (by you, or automatically, e.g. a disconnected mailbox, no email credits, the end date or a failed payment). Resume continues the lead where it was.