Build a Campaign over the API
This guide assembles a complete campaign through the API without any server-side LLM call: a campaign, its ICP, a persona, a roadmap phase and a workflow. You supply the data and the platform stores it, so none of these steps spends credits.
It takes three tool calls: campaignstack_edit_campaign twice and campaignstack_edit_workflow once.
Prerequisites
Section titled “Prerequisites”- An API key with
campaigns:writeandworkflows:write, pluscampaigns:readandworkflows:readto read results back. See Scopes. - Your
workspaceId: callcampaignstack_whoami. With a workspace key (cs_) you can omit it everywhere.
How the edit tools answer
Section titled “How the edit tools answer”Every edit_* tool takes an operations array and applies each operation independently. The response carries one result per operation:
{ "ok": true, "workspaceId": "ws_abc123", "results": [ { "ok": true, "operation": "create", "value": { "campaignId": "camp_xyz789", "title": "Series B Fintech Founders — Q3" } } ]}A failed operation has "ok": false and an error string, and the others still apply. Check every result, not just the top-level ok.
-
Create the campaign
campaignstack_edit_campaign, opcreate, scopecampaigns:write{"workspaceId": "ws_abc123","operations": [{"op": "create","title": "Series B Fintech Founders — Q3","goal": "Book discovery calls with CTOs at fintech companies that raised Series B in the last 18 months","description": "Outbound sequence targeting warm fintech leads via LinkedIn"}]}Take
results[0].value.campaignIdfor the next steps. -
Add the ICP, a persona and a phase in one call
campaignstack_edit_campaign, opsicp,personaandroadmapA campaign has exactly one ICP: the
icpop creates it the first time and updates it after that. Personas attach to that ICP automatically.{"workspaceId": "ws_abc123","operations": [{"op": "icp","campaignId": "camp_xyz789","data": {"titles": ["CTO", "VP Engineering", "Head of Technology"],"industries": ["Financial Services", "Fintech"],"seniorities": ["Director", "VP", "C-Suite"],"companySizes": ["11-50", "51-200", "201-500"],"keywords": ["Series B", "payments", "banking", "embedded finance"]}},{"op": "persona","campaignId": "camp_xyz789","data": {"name": "Technical Co-founder","description": "CTO or VP Eng at a recently funded fintech. Owns the build-vs-buy decision for infrastructure.","painPoints": ["Scaling payment rails without adding headcount"],"goals": ["Ship faster without sacrificing reliability"],"objections": ["We already have a vendor for this"]}},{"op": "roadmap","campaignId": "camp_xyz789","data": {"name": "Initial Outreach","description": "First-touch LinkedIn connection sequence targeting warm leads.","objectives": ["Connect with 50 qualified leads per week"],"phaseType": "outreach","order": 0}}]}Every ICP criteria field is optional. Personas are optional too, but they improve the drafts
campaignstack_craft_messagewrites later. ValidphaseTypevalues:sourcing,outreach,content,intelligence,refinement.The
icpop schedules a rescore of leads that were already scored. To also score leads that never were (for example right after an import), add"rescore": trueto theicpoperation.To keep the campaign fed from LinkedIn posts on a subject, add a
search_watchoperation. It searches LinkedIn posts on one taxonomy topic about every six hours and brings their authors into the campaign. Take the topic frominclude.topicVocabularyofcampaignstack_get_campaigns.accountIdsis optional and picks which of the workspace’s LinkedIn accounts run the search:{ "op": "search_watch", "campaignId": "camp_xyz789", "data": { "name": "fintech", "accountIds": ["li_acc_123"] } }One watch per topic runs in a workspace. When another campaign already watches the topic, the result comes back with
"enabled": false, adisabledReason, and that campaign inrunningOn; this watch starts when the other one stops. Send the same operation with"enabled": falseindatato stop a watch. -
Create the workflow
campaignstack_edit_workflow, opcreate, scopeworkflows:writeFirst call
campaignstack_get_workflow_node_types(no arguments). It returns every node type with itsconfigHint, the graph shape and an example graph. It is the authoritative source: node types are versioned and can change.Then create the workflow with a graph and a source configuration:
{"workspaceId": "ws_abc123","operations": [{"op": "create","campaignId": "camp_xyz789","name": "LinkedIn Connection Sequence","sourceConfig": {"providers": [{"id": "default","type": "campaign_pool","priority": 0,"enabled": true,"config": { "scoreMin": 70, "rules": [], "logic": "and" },"state": { "leadsSourced": 0, "exhausted": false, "dailyCapSourced": 0 }}],"processingOrder": "cascade","globalDailyCap": 20,"exclusions": []},"graph": {"nodes": [{ "nodeType": "linkedin:profile_view", "label": "View Profile", "config": {} },{"nodeType": "flow:wait","label": "Wait 2 days","config": { "waitType": "duration", "durationMs": 172800000 }},{"nodeType": "linkedin:connection_request","label": "Send Connection Request","config": { "noteMode": "none" }}],"edges": [{ "sourceIndex": 0, "targetIndex": 1 },{ "sourceIndex": 1, "targetIndex": 2 }]}}]}This
sourceConfigis the platform default: one Campaign Pool source feeding the campaign’s leads that score at least 70 against its ICP, at most 20 new leads a day. Do not include thesource:entryanchor node innodes; the platform creates it.For a note on the connection request, use
"noteMode": "static"with anoteTemplatesuch as"Hi {{lead:firstName}}, ...". For a personalized AI note, chainai:craft_note→review:content→linkedin:connection_requestwith"noteMode": "static"; theconfigHintof each node explains the wiring.New workflows start paused.
What’s next
Section titled “What’s next”- Import leads:
campaignstack_import_leads(parsed CSV rows, up to 100 per call) orcampaignstack_import_external_leads(Apollo, Instantly or HubSpot). - Score them: run the
icpop again with"rescore": true. - Start the workflow:
campaignstack_edit_workflowwith opset_enabledand"enabled": true. - Monitor:
campaignstack_get_workflowswithinclude.statsfor per-node counts, andinclude.nodeLeads(withnodes.ids) to see who is at a node.campaignstack_diagnose_workflow_sourcingexplains why a given lead did or did not enter. - Change the graph later:
campaignstack_edit_workflow_graph, inpatchorreplacemode. - Send feedback:
campaignstack_submit_feedbackreports issues or requests features directly from your agent.
Signal agent automation
Section titled “Signal agent automation”For event-driven outreach (engage people who just changed jobs, or who engaged with a post), create a signal agent with campaignstack_edit_signal_agent, mode create. Signal agents watch LinkedIn and enqueue matching leads into their own workflow. They are separate from the campaign sequence above; see Signal Agents & Watches.
Troubleshooting
Section titled “Troubleshooting”- A rejected graph comes back with the reason.
campaignstack_get_workflow_node_typesshows the exact config shape each node expects. - An operation that fails leaves the others applied. Fix it and send only that operation again.
campaignstack_submit_feedbackwithcategory: "bug"orcategory: "feature"goes straight to the CampaignStack team.