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
/api/campaignsList 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
| Parameter | Type | Description |
|---|---|---|
status | string | Filter by status (DRAFT, ACTIVE, etc.) |
search | string | Search by campaign name |
folderId | string | Filter by folder ID, or "none" for unfiled campaigns |
cursor | string | Cursor for next page (from previous response) |
limit | number | Results per page (default: 20, max: 50) |
curl --location --request GET 'https://outreach.weezly.com/api/campaigns' \
--header 'X-API-KEY: sk_live_YOUR_API_KEY'{
"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
/api/campaignsCreate a new campaign in DRAFT status. You must add steps and senders before launching.
Request Body
| Parameter | Type | Description |
|---|---|---|
namerequired | string | Campaign name |
leadListIdrequired | string | ID of the lead list to use |
excludeListIds | string[] | Lead list IDs to exclude |
excludeOptions | object | Additional exclusion rules (e.g. leads already in other active campaigns) |
productId | string | Weezly Product whose ICP the campaign scores leads against (IF_ICP_SCORE). Omit for the workspace default. |
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"
]
}'{
"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
| 201 | Campaign created |
| 400 | Name and leadListId required |
| 404 | Lead list not found |
| 401 | Unauthorized |
Get Campaign
/api/campaigns/:idGet 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)
| Parameter | Type | Description |
|---|---|---|
emailsSent | number | Email steps sent. emailReplies, emailsSkipped and emailsBounced are filled only when the sequence has an EMAIL step (0 otherwise). |
opportunities | number | Leads that became an opportunity: an AI-detected interested reply, or the lead entered a positive pipeline stage. Never decreases. |
icpFiltered | number | Leads stopped by an IF_ICP_SCORE gate |
awaitingAcceptance | object|null | Invites sent and waiting for acceptance: count, and until (the earliest withdraw deadline) |
splitTest | object|null | A 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) |
smartReplyMode | string | "off" or "continue_on_ack" |
autoFindEmails | boolean | Whether emails are looked up automatically for leads without one (sequences with email steps) |
leadLocalTime | boolean | Whether message steps go out in each lead's local time |
curl --location --request GET 'https://outreach.weezly.com/api/campaigns/:id' \
--header 'X-API-KEY: sk_live_YOUR_API_KEY'{
"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
/api/campaigns/:idUpdate 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
| Parameter | Type | Description |
|---|---|---|
name | string | Campaign name (any status; must not be empty) |
folderId | string|null | Move to folder, null to unfile (any status) |
prospectValue | number | Dollar value per replied lead, used for opportunity tracking (any status; must be >= 0) |
smartReplyMode | string | "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. |
productId | string|null | Weezly 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. |
autoFindEmails | boolean | Look up emails automatically for leads without one when the sequence has email steps (any status). Lookups use email-finding credits. |
leadLocalTime | boolean | Send 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. |
leadListId | string | Change the source lead list (DRAFT or PAUSED only; must be one of your lists) |
excludeListIds | string[] | Lead list IDs to exclude (DRAFT or PAUSED only) |
excludeOptions | object | Additional exclusion rules (DRAFT or PAUSED only) |
timezone | string | IANA timezone, e.g. "America/New_York" (DRAFT or PAUSED only) |
workingHoursStart | number | Start time in minutes from midnight, e.g. 540 = 9:00 AM (DRAFT or PAUSED only) |
workingHoursEnd | number | End time in minutes from midnight, e.g. 1020 = 5:00 PM (DRAFT or PAUSED only) |
workingDays | number[] | Working days, 0 = Sunday through 6 = Saturday (DRAFT or PAUSED only) |
startDate | string|null | ISO date the campaign may start sending (DRAFT or PAUSED only) |
endDate | string|null | ISO date the campaign stops sending (DRAFT or PAUSED only) |
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"
}'{
"campaign": {
"id": "cm3camp456",
"name": "Updated Campaign Name",
"prospectValue": 500,
"smartReplyMode": "continue_on_ack",
"leadList": { "id": "cm3list789", "name": "SaaS Founders", "leadCount": 150 }
}
}Response Codes
| 200 | Campaign updated |
| 400 | Empty name, invalid smartReplyMode, or targeting/schedule fields sent while not in DRAFT or PAUSED |
| 404 | Campaign, lead list, or exclude list not found |
Update Senders
/api/campaigns/:id/sendersSet 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
| Parameter | Type | Description |
|---|---|---|
accountIdsrequired | string[] | LinkedIn account IDs (from GET /api/accounts). An empty array is accepted on a draft; launch still requires at least one sender. |
emailAccountIds | string[] | 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. |
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"
]
}'{
"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
| 200 | Senders saved |
| 400 | Not 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 |
| 404 | Campaign not found |
Delete Campaign
/api/campaigns/:idDelete a campaign. Only allowed for DRAFT, ARCHIVED, STOPPED, or COMPLETED campaigns.
curl --location --request DELETE 'https://outreach.weezly.com/api/campaigns/:id' \
--header 'X-API-KEY: sk_live_YOUR_API_KEY'{ "success": true }Response Codes
| 200 | Campaign deleted |
| 400 | Cannot delete active campaigns, stop first |
| 404 | Campaign not found |
Launch Campaign
/api/campaigns/:id/launchLaunch 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)
| Parameter | Type | Description |
|---|---|---|
findEmails | boolean | Overrides the campaign's autoFindEmails setting for sequences with email steps |
curl --location --request POST 'https://outreach.weezly.com/api/campaigns/:id/launch' \
--header 'X-API-KEY: sk_live_YOUR_API_KEY'{
"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)
| Parameter | Type | Description |
|---|---|---|
no code (setup) | 400 | Not in DRAFT status, no steps or no senders |
end_date_passed | 400 | The campaign end date is already in the past |
billing_hold | 400 | Every sender is suspended because the subscription payment failed. Includes suspendedSenders. |
seat_limit | 400 | Every LinkedIn sender, or every email sender, is over your seat limit. Includes suspendedSenders and suspendedMailboxes. |
no code (senders) | 400 | None of the selected senders are active (disconnected) |
no code (email) | 400 | The 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) | 400 | A 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) | 502 | The AI video template could not be created, launch aborted |
no code (service) | 503 | Background job service unavailable, campaign reverted to DRAFT |
Response Codes
| 200 | Campaign launched |
| 400 | Launch refused, see the table above |
| 404 | Campaign not found |
| 502 | AI video template could not be created, launch aborted |
| 503 | Background job service unavailable, campaign reverted to DRAFT |
Stop Campaign
/api/campaigns/:id/stopStop an ACTIVE or PAUSED campaign. All pending executions are cancelled.
curl --location --request POST 'https://outreach.weezly.com/api/campaigns/:id/stop' \
--header 'X-API-KEY: sk_live_YOUR_API_KEY'{ "success": true, "status": "STOPPED" }Response Codes
| 200 | Campaign stopped |
| 400 | Only ACTIVE or PAUSED campaigns can be stopped |
| 404 | Campaign not found |
Pause Campaign
/api/campaigns/:id/pausePause an ACTIVE campaign. Executions are suspended but not cancelled.
curl --location --request POST 'https://outreach.weezly.com/api/campaigns/:id/pause' \
--header 'X-API-KEY: sk_live_YOUR_API_KEY'{ "success": true, "status": "PAUSED" }Response Codes
| 200 | Campaign paused |
| 400 | Only ACTIVE campaigns can be paused |
| 404 | Campaign not found |
Resume Campaign
/api/campaigns/:id/resumeResume a PAUSED campaign. Untouched leads are re-planned onto the current schedule; leads mid-sequence keep their timing.
curl --location --request POST 'https://outreach.weezly.com/api/campaigns/:id/resume' \
--header 'X-API-KEY: sk_live_YOUR_API_KEY'{ "success": true, "status": "ACTIVE" }Response Codes
| 200 | Campaign resumed |
| 400 | Not 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) |
| 404 | Campaign not found |
Restart Campaign
/api/campaigns/:id/restartRestart 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)
| Parameter | Type | Description |
|---|---|---|
mode | string | "fresh" (default) or "resume" |
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"
}'{ "success": true, "status": "ACTIVE", "mode": "resume", "reactivated": 42 }Response Codes
| 200 | Campaign restarted. reactivated (resume mode only) is the number of leads put back. |
| 400 | Not 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 |
| 404 | Campaign not found |
| 503 | Background job service unavailable (fresh mode) |
Duplicate Campaign
/api/campaigns/:id/duplicateDuplicate a campaign as a new DRAFT. Copies all steps and settings with remapped IDs.
curl --location --request POST 'https://outreach.weezly.com/api/campaigns/:id/duplicate' \
--header 'X-API-KEY: sk_live_YOUR_API_KEY'{
"success": true,
"campaign": {
"id": "cm3camp_dup123",
"name": "Q3 SaaS Founders Outreach (copy)",
"status": "DRAFT"
}
}Campaign Leads
/api/campaigns/:id/leadsGet leads in a campaign with their execution status, current step, and next step. Failed leads include a human-readable failureReason.
Query Parameters
| Parameter | Type | Description |
|---|---|---|
status | string | Filter 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. |
search | string | Search by lead name, company, job title, or email |
accountId | string | Filter by assigned sender account ID(s), comma-separated |
stepType | string | Filter to leads that completed a step of the given type(s), comma-separated (e.g. CONNECTION_REQUEST,MESSAGE) |
connected | string | "yes" = only 1st-degree connections, "no" = only leads not yet connected |
page | number | Page number (default: 1) |
limit | number | Results per page (default: 25) |
curl --location --request GET 'https://outreach.weezly.com/api/campaigns/:id/leads' \
--header 'X-API-KEY: sk_live_YOUR_API_KEY'{
"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
/api/campaigns/:id/leads/:executionIdRemove 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
| Parameter | Type | Description |
|---|---|---|
keepInList | string | Set 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. |
curl --location --request DELETE 'https://outreach.weezly.com/api/campaigns/:id/leads/:executionId' \
--header 'X-API-KEY: sk_live_YOUR_API_KEY'{ "ok": true }Response Codes
| 200 | Lead removed from the campaign |
| 404 | Campaign not found, or lead not in this campaign |
| 401 | Unauthorized |
Campaign Stats
/api/campaigns/:id/statsGet daily activity stats for a campaign.
Query Parameters
| Parameter | Type | Description |
|---|---|---|
days | number | Number of days of history (default: 7) |
curl --location --request GET 'https://outreach.weezly.com/api/campaigns/:id/stats' \
--header 'X-API-KEY: sk_live_YOUR_API_KEY'{
"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
/api/campaigns/:id/stepsGet all campaign sequence steps with their configuration.
curl --location --request GET 'https://outreach.weezly.com/api/campaigns/:id/steps' \
--header 'X-API-KEY: sk_live_YOUR_API_KEY'{
"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
/api/campaigns/:id/stepsReplace 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
| Parameter | Type | Description |
|---|---|---|
stepsrequired | object[] | The full array of sequence steps. On a started campaign, every step must keep its existing id, type, order, branchId, and parentStepId. |
Step Fields
| Parameter | Type | Description |
|---|---|---|
id | string | Step ID. Branch IDs are built from it, so send your own stable IDs when you create branches. |
typerequired | string | One of the step types listed under Campaign Step Types |
orderrequired | number | Position within its branch, 1..N with no gaps per branch |
branchId | string|null | null 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) |
parentStepId | string|null | The forking step a branch belongs to |
delayDays / delayHours / delayMinutes | number | Wait before this step, measured from the previous step (randomized by about 20%). The step right after an IF_ICP_SCORE gate never waits. |
inviteNote | string | CONNECTION_REQUEST note (may use {{variables}}) |
withdrawAfterDays | number | CONNECTION_REQUEST: withdraw the invite if not accepted after N days (0 = never) |
messageText | string | Text of MESSAGE, INMAIL and EMAIL steps (may use {{variables}}) |
fallbackMessage | string | Sent instead when a {{variable}} cannot be filled for the lead. Required when the text uses variables. |
messageVariations | object[] | A/B message variations: [{ messageText, fallbackMessage }]. Replaces messageText when present. |
emailSubject | string | EMAIL subject, also the optional InMail subject. On a follow-up EMAIL, empty = reply in the same thread. |
emailSubjectFallback | string | Subject used when a subject variable cannot be filled (no variables allowed in it) |
attachmentUrl / attachmentName | string | MESSAGE: file sent with the text in one message (image, PDF, Office document, TXT or CSV, max 20 MB) |
videoUrl | string | VIDEO_MESSAGE: recorded video URL |
voiceUrl | string | VOICE_NOTE: voice recording URL |
reactionType | string | REACT_TO_POST: like, celebrate, support, love, insightful or funny |
commentTone | string | COMMENT_ON_POST: supportive, professional, excited, curious, congratulatory or thoughtful |
commentLength | string | COMMENT_ON_POST: ultra_short, super_short, short, medium or long |
commentEmojis / commentRules | boolean / string[] | COMMENT_ON_POST: allow emojis, extra instructions for the AI |
icpMinScore | number | IF_ICP_SCORE: minimum ICP score 1 to 10 to continue (6 = good fit and above) |
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
}
]
}'{
"steps": [
{ "id": "step1", "type": "CONNECTION_REQUEST", "order": 1, "inviteNote": "Hi {{firstName}}, updated note..." }
]
}{
"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
| 200 | Steps saved |
| 400 | steps must be an array |
| 404 | Campaign not found |
| 409 | STRUCTURE_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
/api/campaign-foldersRetrieve all campaign folders for the authenticated user, in display order.
curl --location --request GET 'https://outreach.weezly.com/api/campaign-folders' \
--header 'X-API-KEY: sk_live_YOUR_API_KEY'{
"folders": [
{
"id": "cm3cfolder123",
"name": "Q3 Campaigns",
"emoji": "🎯",
"position": 0,
"_count": { "campaigns": 3 }
}
]
}Response Codes
| 200 | Folders returned |
| 401 | Unauthorized |
Create Folder
/api/campaign-foldersCreate a new campaign folder. Folder names must be unique per user.
Request Body
| Parameter | Type | Description |
|---|---|---|
namerequired | string | Folder name |
emoji | string | Folder emoji (defaults to 📁) |
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": "🎯"
}'{
"folder": {
"id": "cm3cfolder123",
"name": "Q3 Campaigns",
"emoji": "🎯",
"position": 0,
"_count": { "campaigns": 0 }
}
}Response Codes
| 201 | Folder created |
| 400 | Folder name is required |
| 409 | A folder with this name already exists |
Update Folder
/api/campaign-folders/:idRename a folder or change its emoji.
Request Body
| Parameter | Type | Description |
|---|---|---|
name | string | New folder name |
emoji | string | New folder emoji |
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"
}'{ "success": true }Response Codes
| 200 | Folder updated |
| 404 | Folder not found |
| 409 | A folder with this name already exists |
Delete Folder
/api/campaign-folders/:idDelete a folder. The campaigns inside it are not deleted, they become unfiled.
curl --location --request DELETE 'https://outreach.weezly.com/api/campaign-folders/:id' \
--header 'X-API-KEY: sk_live_YOUR_API_KEY'{ "success": true }Response Codes
| 200 | Folder deleted |
| 404 | Folder not found |
Campaign Step Types
| Type | Description |
|---|---|
| CONNECTION_REQUEST | Send a LinkedIn connection request with an optional note (inviteNote). Branches: _true = accepted, _false = not accepted yet. Free LinkedIn plans always send without a note. |
| MESSAGE | Send a LinkedIn message to a connected lead, optionally with a file attachment. Branches: _true = replied, _false = no reply yet. |
| VIDEO_MESSAGE | Send a video message, either a recorded video (videoUrl) or an AI-generated personalized video |
| VOICE_NOTE | Send a voice note (voiceUrl) |
| INMAIL | Send an InMail to a lead you are not connected with, with an optional subject. A connected lead gets a regular message instead. |
| Send an email from one of the campaign's email senders (emailSubject + messageText). Requires email steps on your workspace. | |
| VIEW_PROFILE | View the lead's LinkedIn profile |
| LIKE_POST | Like the lead's latest post |
| REACT_TO_POST | React to the lead's latest post with the chosen reactionType |
| COMMENT_ON_POST | Post an AI-written comment on the lead's latest post |
| FOLLOW_PROFILE | Follow the lead's LinkedIn profile |
| IF_CONNECTION | Condition: _true = the lead is a 1st-degree connection, _false = not connected |
| IF_OPEN_PROFILE | Condition on the lead's open profile. Currently every lead takes the _false (not open) path. |
| IF_HAS_EMAIL | Condition: _true = the lead has a usable email (not bounced, not unsubscribed), _false = no email |
| IF_ICP_SCORE | Gate, 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_TEST | A/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. |
| END | End the sequence for the lead |
Execution Statuses
| Status | Description |
|---|---|
| PENDING | Not yet started, waiting for its scheduled time |
| ACTIVE | Currently being processed |
| WAITING | Waiting for the delay between steps |
| COMPLETED | All steps finished successfully |
| FAILED | A step failed after retries |
| STOPPED_REPLY | Lead replied, the sequence stopped automatically |
| STOPPED | The campaign was stopped before this lead finished |
| PAUSED | The 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. |