This is the full developer documentation for CampaignStack # CampaignStack Documentation > Connect AI agents to CampaignStack via MCP. Manage campaigns, leads, workflows, and content programmatically. 200+ MCP Tools Full control over campaigns, leads, workflows, ICPs, content, and more — all via the Model Context Protocol. Agent-First Built for AI agents. Rich descriptions, cross-referenced errors, and structured JSON responses help LLMs chain operations effectively. Scope-Based Auth Fine-grained API keys with per-domain read/write scopes. Grant only the access your agent needs. Works Everywhere Claude Desktop, Cursor, custom integrations — one hosted HTTP endpoint, any MCP client. # Concepts > Core domain concepts in CampaignStack. ## Domain model [Section titled “Domain model”](#domain-model) CampaignStack organizes outreach around these core entities, with the **workspace** as the tenant boundary for everything you create. ### Workspaces [Section titled “Workspaces”](#workspaces) A workspace represents a client or business. Your campaigns, lead lists, workflows, content, competitor watches, settings, and workspace API keys live inside it. If you’re an agency, you’ll typically have one workspace per client. If you’re a solo operator, you have one workspace for your business. Most MCP tools take a `workspaceId` parameter. Call `campaignstack_whoami` first — with a user key (`csu_`) it returns every workspace you belong to, with IDs and roles. ### Campaigns [Section titled “Campaigns”](#campaigns) A campaign is a goal-driven outreach initiative inside a workspace. It defines who you’re targeting, why, and how. Campaigns contain lead lists, workflows, ICPs, personas, and phases. **Example:** “Connect with wealth management CTOs in France to demo our compliance tool.” ### Personas & Phases [Section titled “Personas & Phases”](#personas--phases) **Personas** describe the buyer profiles a campaign targets (role, pains, goals) and inform AI message crafting. **Phases** structure a campaign’s roadmap over time — sourcing, outreach, content, intelligence, refinement. Both have full CRUD tools. ### Leads [Section titled “Leads”](#leads) People you want to reach. CampaignStack maintains a **shared lead database**: leads sourced from public platforms (LinkedIn search, enrichment) are readable across the platform, so every campaign benefits from data that already exists — the same model Apollo uses. Leads you supply yourself — CSV imports, manual entry, or imports via the MCP API — are **private to your workspace** and never enter the shared pool. Each lead carries profile data (title, company, location, seniority) that gets enriched automatically, plus per-workspace tags and notes that only your workspace sees. ### Lead Lists [Section titled “Lead Lists”](#lead-lists) Named collections of leads in your workspace, optionally attached to a campaign. Two kinds: **static lists** (you add/remove leads explicitly) and **query lists** (a stored search predicate that re-materializes automatically as matching leads appear). ### ICPs (Ideal Customer Profiles) [Section titled “ICPs (Ideal Customer Profiles)”](#icps-ideal-customer-profiles) Scoring criteria that define your ideal lead: target titles, industries, seniority levels, company sizes, keywords. A lead’s score for a campaign is its ICP match **plus its signal score** — and that total determines whether the lead is part of the campaign at all, not just its priority. ### Signals & Signal Agents [Section titled “Signals & Signal Agents”](#signals--signal-agents) **Signals** are detected changes on leads and companies — job change, new post, funding round, headcount growth. They boost lead scores (per-campaign weights are tunable) and expire after a set window. **Signal agents** watch for signals continuously and can route matching leads into workflows automatically — audiences that build themselves. ### Workflows [Section titled “Workflows”](#workflows) Automated multi-step sequences that execute outreach actions across channels. A workflow might: send a connection request, wait 3 days, send a follow-up message, A/B split, notify you on reply. Workflows are built from nodes (actions) and edges (transitions). They respect daily action budgets and business hours. LinkedIn sends can be gated behind human review — you approve, edit, or reject each message before it goes out. ### Inbox & Conversations [Section titled “Inbox & Conversations”](#inbox--conversations) Replies land in a unified inbox covering all connected accounts. Conversations are account-scoped: one inbox per LinkedIn account, shared across the workspaces that account is linked to. ### Content Posts [Section titled “Content Posts”](#content-posts) LinkedIn content that your accounts publish. Posts go through a lifecycle: draft, review, scheduled, published. Brand voice and AI writing tools ensure consistency, and auto-scheduling places posts into budget-safe slots. ### Competitors [Section titled “Competitors”](#competitors) Monitored LinkedIn company or profile pages. CampaignStack tracks their posts, engagement metrics, and topics to inform your content strategy. ### Playbooks [Section titled “Playbooks”](#playbooks) Workspace-level conversation guidance that AI crafting follows — tone, positioning, objection handling. Campaigns can append extra instructions. See the [playbooks guide](/guides/playbooks/). ### Connected Accounts & Credits [Section titled “Connected Accounts & Credits”](#connected-accounts--credits) LinkedIn and Google accounts power sending; one account can be linked to several workspaces. Every billable action (enrichment, scraping, AI crafting) consumes **credits** from your workspace balance — check it with `campaignstack_get_credit_balance`. ## How they connect [Section titled “How they connect”](#how-they-connect) ```plaintext Workspace (= one client / business) ├── Campaigns (goal + strategy) │ ├── Lead Lists (segments → leads, scored per campaign) │ ├── ICPs (scoring criteria) │ ├── Personas & Phases (targeting + roadmap) │ └── Workflows (automation sequences) │ └── Nodes (actions: connect, message, email, review...) ├── Signal Agents (self-building audiences) ├── Content Posts (LinkedIn publishing) ├── Competitor Watches (monitoring) └── Connected Accounts (LinkedIn + Google, shareable across workspaces) Shared platform data (readable by all tenants): Leads ── Companies ── Signals ``` ## Scopes map to domains [Section titled “Scopes map to domains”](#scopes-map-to-domains) Each API key carries scopes following the `{domain}:{action}` pattern — `campaigns:read`, `leads:write`, `outreach:write`, and so on across 16 domains. Your key’s scopes determine which tools it can call. See the [full scope reference](/mcp/scopes/) for every scope and its tool mapping. # Introduction > What CampaignStack is and why it exists. CampaignStack is an AI-powered campaign orchestration platform. It helps lead generation agencies run multi-channel outreach campaigns — from sourcing leads to crafting personalized messages to tracking engagement. ## What makes it different [Section titled “What makes it different”](#what-makes-it-different) CampaignStack is **MCP-first**. Instead of a traditional REST API with endpoints you call manually, CampaignStack exposes its entire functionality through the [Model Context Protocol](https://modelcontextprotocol.io/) (MCP). This means AI agents can discover, understand, and use all available tools without custom integration code. ## What you can do [Section titled “What you can do”](#what-you-can-do) * **Campaigns** — Create and manage outreach campaigns with AI-generated strategies, personas, and phases * **Leads** — Search, import, enrich, and score leads against your Ideal Customer Profile * **Workflows** — Design and trigger automated multi-step outreach sequences with human review gates * **Outreach** — Send connection requests, messages, InMail, and emails within per-account safety budgets * **Inbox** — Read and reply to conversations across all connected accounts in one place * **AI crafting** — Generate messages, connection notes, comments, and follow-ups that follow your workspace playbook * **Signals** — Detect job changes, posts, and funding events; build self-updating audiences with signal agents * **Content** — Create, schedule, and publish LinkedIn posts with brand voice consistency * **Calendar & email** — Check availability, book meetings, send Gmail, and warm up sending accounts * **Competitors** — Monitor competitor activity and engagement * **Integrations** — Import from Apollo, connect Instantly, and wire webhooks (Zapier, Clay, n8n) ## Architecture [Section titled “Architecture”](#architecture) ```plaintext Your AI Agent ←→ https://mcp.campaignstack.io/mcp ←→ CampaignStack ``` The hosted MCP server sits between your agent and CampaignStack. It handles authentication, scope enforcement, input validation, and returns structured JSON responses. ## Next steps [Section titled “Next steps”](#next-steps) * [Quick Start](/getting-started/quick-start/) — Connect and make your first tool call * [Concepts](/getting-started/concepts/) — Understand the domain model * [Authentication](/mcp/authentication/) — Set up API keys and scopes # Quick Start > Connect to CampaignStack and make your first tool call in under 5 minutes. ## Connect to CampaignStack [Section titled “Connect to CampaignStack”](#connect-to-campaignstack) 1. **Get an API key** Go to **Settings > API Keys** and create a user key. Select the scopes you need (or “Select all” to start). Copy the key — it starts with `csu_` and is shown only once. (Workspace-bound `cs_` keys also exist — see [Authentication](/mcp/authentication/).) 2. **Connect your MCP client** * Claude Desktop Add to your Claude Desktop config (`claude_desktop_config.json`) or Claude Code settings: ```json { "mcpServers": { "campaignstack": { "url": "https://mcp.campaignstack.io/mcp", "headers": { "Authorization": "Bearer csu_your_api_key_here" } } } } ``` * Cursor In Cursor settings, add an MCP server: * **Name:** `campaignstack` * **URL:** `https://mcp.campaignstack.io/mcp` * **Headers:** `Authorization: Bearer csu_your_api_key_here` * curl Test connectivity (no auth needed — returns server status and the tool list): ```bash curl https://mcp.campaignstack.io/health ``` Test your API key with an MCP `initialize` handshake: ```bash curl -X POST https://mcp.campaignstack.io/mcp \ -H "Content-Type: application/json" \ -H "Accept: application/json, text/event-stream" \ -H "Authorization: Bearer csu_your_api_key_here" \ -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"curl","version":"1.0"}}}' ``` Subsequent requests must include the `mcp-session-id` header returned by the initialize response. 3. **Verify the connection** Call the `campaignstack_whoami` tool. It returns your available scopes and the workspaces you belong to: ```json { "keyType": "user", "keyPrefix": "csu_a1b2", "scopes": ["campaigns:read", "campaigns:write", "leads:read"], "user": { "id": "...", "name": "...", "email": "..." }, "workspaces": [ { "workspaceId": "abc123", "name": "My Agency", "role": "owner" } ] } ``` 4. **Make your first real call** Try listing your campaigns: ```plaintext Use the campaignstack_list_campaigns tool with workspaceId from whoami ``` The response includes campaign IDs you can use with `campaignstack_get_campaign`, `campaignstack_list_workflows`, and other tools. Note A user key (`csu_`) works across all your workspaces — pass the target `workspaceId` per tool call. Workspace keys (`cs_`) are bound to one workspace each. See [Authentication](/mcp/authentication/#multi-workspace-setups) for details. ## What’s next [Section titled “What’s next”](#whats-next) * [Concepts](/getting-started/concepts/) — Understand campaigns, leads, workflows, ICPs * [Authentication](/mcp/authentication/) — API key format, scopes, security * [Tool Reference](/mcp/tools/campaigns/) — Full reference for all 200+ tools # Use with any AI > LLM-friendly documentation files — llms.txt, llms-full.txt, and SKILL.md — for connecting any AI assistant to CampaignStack. CampaignStack publishes machine-readable versions of its documentation so any AI assistant can learn the platform without you copy-pasting docs. ## SKILL.md — the fastest way [Section titled “SKILL.md — the fastest way”](#skillmd--the-fastest-way) [`SKILL.md`](https://docs.campaignstack.io/SKILL.md) is a self-contained skill file: what CampaignStack is, how to connect to the MCP server, the core concepts, and the main tool workflows. Give it to your AI assistant and it knows how to operate CampaignStack. * **Claude Code / Claude Desktop**: save it as a [skill](https://code.claude.com/docs/en/skills), or just paste the URL and ask Claude to read it * **Cursor, ChatGPT, or any other assistant**: paste the file contents (or the URL, if the assistant can browse) at the start of a conversation Also available at [campaignstack.io/SKILL.md](https://campaignstack.io/SKILL.md). ## llms.txt — for AI crawlers and agents [Section titled “llms.txt — for AI crawlers and agents”](#llmstxt--for-ai-crawlers-and-agents) Following the [llms.txt standard](https://llmstxt.org/): | File | Contents | | --------------------------------------------------------------- | ------------------------------------------------------------------------- | | [/llms.txt](https://docs.campaignstack.io/llms.txt) | Index of the documentation with links | | [/llms-full.txt](https://docs.campaignstack.io/llms-full.txt) | The complete documentation in one file, including the full tool reference | | [/llms-small.txt](https://docs.campaignstack.io/llms-small.txt) | Abridged version for smaller context windows | The main website publishes its own index at [campaignstack.io/llms.txt](https://campaignstack.io/llms.txt). These files regenerate automatically from the same source as this documentation site, so they are never out of date. ## Which one should I use? [Section titled “Which one should I use?”](#which-one-should-i-use) * **Setting up an AI assistant to use CampaignStack for you** → `SKILL.md` * **Building an agent or integration and want full API detail** → `llms-full.txt` * **Tight on context** → `llms-small.txt` # Managing API Keys > Create, manage, and revoke API keys for the CampaignStack MCP server. ## Key types [Section titled “Key types”](#key-types) | Key type | Prefix | Bound to | Created in | | ----------------- | ------ | ------------------------------------------------ | --------------------------------- | | **User key** | `csu_` | Your user account — all workspaces you belong to | **Settings > API Keys** | | **Workspace key** | `cs_` | A single workspace | **Workspace Settings > API Keys** | For personal AI assistants (Claude Desktop, Cursor), create a **user key** — one key covers every workspace you’re a member of. Create a workspace key when an integration should be locked to a single workspace. Only the **workspace owner** can create workspace keys; any member can revoke them. ## Creating a key [Section titled “Creating a key”](#creating-a-key) 1. Navigate to **Settings > API Keys** (user key) or **Workspace Settings > API Keys** (workspace key) 2. Click **Create API Key** 3. Enter a name (e.g., “Claude Desktop”, “Production Agent”) 4. Select scopes — toggle read/write per domain, or use “Select all” 5. Click **Create Key** 6. **Copy the key immediately** — it’s shown only once Caution The full API key is displayed only at creation time. CampaignStack stores a SHA-256 hash for security. If you lose the key, revoke it and create a new one. ## Key format [Section titled “Key format”](#key-format) ```plaintext csu_a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4 ``` * Prefix: `csu_` (user keys) or `cs_` (workspace keys) * Body: 32 hex characters (16 bytes of cryptographic randomness) ## Viewing keys [Section titled “Viewing keys”](#viewing-keys) The API Keys page shows all active keys with: * **Name** — your label for the key * **Key prefix** — first 8 characters (`csu_a1b2` / `cs_a1b2c`) for identification * **Scopes** — which permissions are granted * **Created** — when the key was created * **Last used** — when the key was last used for authentication ## Revoking a key [Section titled “Revoking a key”](#revoking-a-key) Click the revoke button next to any key. Revocation is immediate and permanent — the key stops working instantly. Revoked keys are soft-deleted (marked with a `revokedAt` timestamp) and no longer appear in the key list. ## Best practices [Section titled “Best practices”](#best-practices) * **One key per integration** — create separate keys for Claude Desktop, Cursor, production agents, etc. * **Minimum scopes** — grant only the scopes each integration needs * **Rotate regularly** — revoke and recreate keys periodically * **Never commit keys** — use environment variables or secret managers # Build a Campaign over the API > Step-by-step guide to assembling a campaign programmatically using free primitive tools — no server LLM required. This guide walks through the **free primitive sequence**: creating a complete campaign configuration via the API without triggering any server-side LLM calls. All five tools in this sequence (`create_campaign`, `create_icp`, `create_persona`, `create_phase`, `create_workflow`) are credit-free — you supply the data, the platform stores it. Guided generation is in-product only The automated campaign generation flow (analyze website → synthesize goal → auto-generate workflow) runs inside the CampaignStack web app and is not available through the MCP API. If you want signal-driven automation, see [Signal Agents](#signal-agent-automation) below. ## Prerequisites [Section titled “Prerequisites”](#prerequisites) * An API key with `campaigns:write`, `icps:write`, and `workflows:write` scopes. See [API Keys](/guides/api-keys/). * Your `workspaceId` — call `campaignstack_whoami` to retrieve it. ## The sequence [Section titled “The sequence”](#the-sequence) ```plaintext create_campaign └── create_icp (criteria for lead scoring) └── create_persona (buyer archetype) └── create_phase (roadmap milestone) └── create_workflow (automation graph) ``` All five calls are independent after `create_campaign` returns a `campaignId`. 1. **Create the campaign** `campaignstack_create_campaign` — scope `campaigns:write` ```json { "workspaceId": "ws_abc123", "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" } ``` Returns: ```json { "campaignId": "camp_xyz789", "title": "Series B Fintech Founders — Q3" } ``` Use `campaignId` in every subsequent call. 2. **Define an ICP** `campaignstack_create_icp` — scope `icps:write` — **free, no LLM** ICP criteria control how leads are scored. Omit any field you don’t need — all criteria fields are optional. ```json { "campaignId": "camp_xyz789", "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"] } ``` Returns: ```json { "icpId": "icp_def456" } ``` After leads are imported, call `campaignstack_trigger_icp_scoring` to compute scores. Leads scoring above the workflow’s threshold (default 50, configurable via `scoreMin`) are fed to workflows automatically via the `campaign_pool` source. 3. **Create a persona** `campaignstack_create_persona` — scope `campaigns:write` — **free, no LLM** Personas are buyer archetypes used to tailor message copy. They are optional but improve `craft_message` output quality when you later draft outreach. ```json { "campaignId": "camp_xyz789", "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", "Compliance overhead from new banking regulations" ], "goals": [ "Ship faster without sacrificing reliability", "Reduce operational cost of treasury ops" ], "objections": [ "We already have a vendor for this", "Not the right time — just closed our round" ] } ``` Returns: ```json { "personaId": "persona_ghi012" } ``` 4. **Add a phase** `campaignstack_create_phase` — scope `campaigns:write` — **free, no LLM** Phases are roadmap milestones that organize a campaign into sequential steps. At least one phase is recommended for clarity. ```json { "campaignId": "camp_xyz789", "name": "Initial Outreach", "description": "First-touch LinkedIn connection sequence targeting warm leads.", "objectives": [ "Connect with 50 qualified leads per week", "Achieve >25% connection acceptance rate", "Generate 5 discovery call bookings" ], "phaseType": "outreach", "order": 0 } ``` Returns: ```json { "phaseId": "phase_jkl345" } ``` Valid `phaseType` values: `sourcing`, `outreach`, `content`, `intelligence`, `refinement`. 5. **Create a workflow** `campaignstack_create_workflow` — scope `workflows:write` — **free, no LLM** Before writing a graph, call `campaignstack_get_workflow_node_types` to discover all available node types, their `configHint` shapes, and the required graph format. This is the authoritative source — node types are versioned and can change. ```json { } ``` Returns `{ nodeTypes, graphShape, exampleGraph }`. Once you have the vocabulary, author your graph: ```json { "campaignId": "camp_xyz789", "name": "LinkedIn Connection Sequence", "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": "static", "noteTemplate": "Hi {{lead:firstName}}, I noticed your work at {{lead:company}} — would love to connect." } } ], "edges": [ { "sourceIndex": 0, "targetIndex": 1 }, { "sourceIndex": 1, "targetIndex": 2 } ] } } ``` Returns: ```json { "workflowId": "wf_mno678", "nodeCount": 4 } ``` `nodeCount` includes the hidden anchor (`source:entry`) node that the platform prepends automatically. Tip Omit `sourceConfig` to use the default: a `campaign_pool` source that feeds ICP-scored leads with a minimum score of 50 and a global daily cap of 20. Supply `sourceConfig` explicitly only if you need different thresholds. ## What’s next [Section titled “What’s next”](#whats-next) After the campaign is configured: * **Import leads** — use `campaignstack_import_leads_csv`, `campaignstack_import_apollo_list`, or `campaignstack_create_query_lead_list` to populate the lead pool. * **Score leads** — call `campaignstack_trigger_icp_scoring` with the `icpId` to compute match scores. * **Enable the workflow** — call `campaignstack_update_workflow_status` with `enabled: true` to start processing leads. * **Monitor** — use `campaignstack_get_workflow_stats` and `campaignstack_list_leads_at_node` to track progress. * **Send feedback** — call `campaignstack_submit_feedback` to report issues or request features directly from your agent. ## Signal agent automation [Section titled “Signal agent automation”](#signal-agent-automation) If you want signal-driven, event-triggered outreach (e.g. engage leads who just changed jobs or liked a post), use `campaignstack_create_signal_agent` instead of a manual workflow. Signal agents monitor LinkedIn signals and automatically enqueue matching leads into workflows. They are a separate concept from the manual campaign primitive sequence described in this guide. ## Troubleshooting and feedback [Section titled “Troubleshooting and feedback”](#troubleshooting-and-feedback) * Call `campaignstack_get_workflow_node_types` if your graph is rejected — the `configHint` field shows the exact required shape for each node type. * Call `campaignstack_submit_feedback` with `category: "bug"` or `category: "feature_request"` to report issues. Feedback goes directly to the CampaignStack team. # Claude Desktop > Connect CampaignStack to Claude Desktop via MCP. ## Setup [Section titled “Setup”](#setup) 1. **Get an API key** In CampaignStack, go to **Settings > API Keys** and create a user key (`csu_`) with the scopes you need. 2. **Add to Claude Desktop config** Connect to the hosted MCP server: ```json { "mcpServers": { "campaignstack": { "url": "https://mcp.campaignstack.io/mcp", "headers": { "Authorization": "Bearer csu_your_api_key_here" } } } } ``` 3. **Verify** Open Claude Desktop and ask: “Use CampaignStack to list my campaigns.” Claude should call `campaignstack_whoami` first (to get the workspace ID), then `campaignstack_list_campaigns`. ## Example prompts [Section titled “Example prompts”](#example-prompts) Once connected, try these: * “List all my campaigns and show their status” * “Search for leads with ‘CTO’ in their title” * “Create a new campaign targeting fintech founders in Paris” * “What content posts are scheduled for this week?” * “Draft a LinkedIn connection message for this lead” # Cursor > Connect CampaignStack to Cursor IDE via MCP. ## Setup [Section titled “Setup”](#setup) 1. **Get an API key** In CampaignStack, go to **Settings > API Keys** and create a user key (`csu_`) with the scopes you need. 2. **Add MCP server in Cursor** Open Cursor settings and navigate to the MCP servers section. Add a new server: * **Name:** `campaignstack` * **Type:** HTTP * **URL:** `https://mcp.campaignstack.io/mcp` * **Headers:** ```plaintext Authorization: Bearer csu_your_api_key_here ``` 3. **Verify** In Cursor’s chat, ask: “Use CampaignStack to check my connection with whoami.” ## Usage tips [Section titled “Usage tips”](#usage-tips) * Cursor shows available MCP tools in the chat sidebar * You can reference CampaignStack tools directly: “Use campaignstack\_search\_leads to find CTOs in fintech” * Combine with code context: “Look at this lead list and draft personalized messages for each” # Conversation Playbooks > How workspace, campaign, and signal-agent playbooks steer every AI-written message — and how the three levels combine. A playbook tells CampaignStack’s AI how to talk on your behalf: tone, key talking points, boundaries, topics to avoid, and when to hand a conversation to a human. Every AI-crafted message — connection notes, first messages, follow-ups, comment replies, emails, and automated inbox responses — is written with your playbook in the prompt. There are three levels. Two of them are real playbooks; one is an add-on. ## The three levels [Section titled “The three levels”](#the-three-levels) ### Workspace playbook [Section titled “Workspace playbook”](#workspace-playbook) The canonical playbook, one per workspace. Generate it with AI from your campaigns, ICPs, personas, and company context, then edit it by hand as much as you like. * **Edit:** Workspace → **Playbook** tab * **API:** `campaignstack_get_playbook` / `campaignstack_regenerate_playbook` Caution Regenerating **overwrites the entire playbook, including your manual edits**. Regenerate first, then re-apply your edits on top. ### Campaign playbook override [Section titled “Campaign playbook override”](#campaign-playbook-override) Campaigns don’t have standalone playbooks. The campaign-level field is an **override that appends**: its text is attached to the workspace playbook under *“Additional instructions for this campaign:”*. Use it for campaign-specific offers, ICP-specific messaging, or audience-building goals — anything that supplements (not replaces) your workspace-wide voice. * **Edit:** Campaign → **Settings** → *Playbook override* ### Signal agent playbook [Section titled “Signal agent playbook”](#signal-agent-playbook) Signal agents (which handle inbound messages and post engagement autonomously) can have their own playbook. Unlike the campaign override, this one **fully replaces** the workspace playbook when set. Leave it empty and the agent falls back to the workspace playbook. Signal agents never see campaign overrides — they don’t belong to campaigns. * **Edit:** Signal agent dashboard → *Playbook* editor ## How the levels combine [Section titled “How the levels combine”](#how-the-levels-combine) For any given AI message, CampaignStack resolves exactly one playbook text: 1. **Signal agent conversation?** The agent’s playbook is used alone (if set). Nothing else is included. 2. **Campaign conversation?** Workspace playbook + campaign override, appended in that order. 3. **Otherwise:** the workspace playbook alone. | You set… | The AI sees… | | ---------------------------------------------- | ----------------------------------------------------------------------- | | Workspace only | Workspace playbook | | Workspace + campaign override | Workspace playbook, then “Additional instructions for this campaign: …” | | Signal agent playbook (agent conversations) | Agent playbook only — workspace ignored | | Campaign override only (no workspace playbook) | The override alone | ## What the playbook influences [Section titled “What the playbook influences”](#what-the-playbook-influences) * **Message content** — first messages, connection notes, follow-ups, comment replies, and emails are all generated with the playbook in context. * **Automated reply behavior** — inbox-handling agents use the playbook to decide when to respond, what tone to use, and when to escalate a conversation to you instead of replying. * **Boundaries** — pricing rules, topics to avoid, and escalation triggers you write into the playbook are enforced at generation time, on every path. Tip Write escalation rules explicitly (“Never discuss pricing — escalate to a human”) — the automated agents follow them when deciding between replying and handing off. # Authentication > API key format, creation, and scope-based access control. ## API Keys [Section titled “API Keys”](#api-keys) CampaignStack uses API keys for authentication. There are two key types: | Key type | Prefix | Bound to | Created in | | ----------------- | ------ | ------------------------------------------------------------- | --------------------------------- | | **User key** | `csu_` | Your user account — works across all workspaces you belong to | **Settings > API Keys** | | **Workspace key** | `cs_` | A single workspace | **Workspace Settings > API Keys** | User keys are the usual choice for personal AI assistants (Claude Desktop, Cursor): one key covers every workspace you’re a member of. Workspace keys are for integrations that should be locked to one workspace, such as a production agent serving a single client. ### Key format [Section titled “Key format”](#key-format) ```plaintext csu_a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4 └──┘└──────────────────────────────────┘ prefix 32 hex characters ``` Keys are a prefix (`csu_` for user keys, `cs_` for workspace keys) followed by 32 random hex characters (16 bytes of entropy). ### Creating a key [Section titled “Creating a key”](#creating-a-key) 1. Go to **Settings > API Keys** (user key) or **Workspace Settings > API Keys** (workspace key) 2. Click **Create API Key** 3. Name the key and select scopes 4. Copy the key immediately — it’s shown only once Caution The plaintext key is displayed only at creation time. CampaignStack stores a SHA-256 hash — the original key cannot be retrieved later. ### Using a key [Section titled “Using a key”](#using-a-key) Pass the key as a Bearer token in the `Authorization` header: ```bash curl -X POST https://mcp.campaignstack.io/mcp \ -H "Content-Type: application/json" \ -H "Accept: application/json, text/event-stream" \ -H "Authorization: Bearer csu_your_api_key_here" \ -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"curl","version":"1.0"}}}' ``` The MCP protocol requires an `initialize` handshake first (the response carries an `mcp-session-id` header to include on subsequent requests) and an `Accept` header listing both `application/json` and `text/event-stream`. For a quick unauthenticated connectivity check, `GET /health` returns the server status and tool list. ### Workspace targeting [Section titled “Workspace targeting”](#workspace-targeting) With a **workspace key** (`cs_`), the workspace is implicit — every tool call operates on the key’s workspace. With a **user key** (`csu_`), workspace-scoped tools require an explicit `workspaceId` argument. Call `campaignstack_whoami` first: it returns the workspaces you belong to, with their IDs and your role in each. ### Key lifecycle [Section titled “Key lifecycle”](#key-lifecycle) * **Active** — key is valid and can authenticate requests * **Revoked** — key is permanently disabled (soft delete via `revokedAt` timestamp) * Keys track `lastUsedAt` for auditing * Validation results are cached in-memory for 5 seconds, so revoking a key takes effect within \~5 seconds ## Scopes [Section titled “Scopes”](#scopes) Each API key is granted a set of scopes that control which tools it can call. Scopes follow the pattern `{domain}:{action}`. See the [Scopes reference](/mcp/scopes/) for the full permission matrix. ## Verifying access [Section titled “Verifying access”](#verifying-access) Call `campaignstack_whoami` to check your current permissions. With a user key it also lists your workspaces: ```json { "keyType": "user", "keyPrefix": "csu_a1b2", "scopes": ["campaigns:read", "campaigns:write", "leads:read"], "user": { "id": "...", "name": "...", "email": "..." }, "workspaces": [ { "workspaceId": "abc123", "name": "My Agency", "role": "owner" }, { "workspaceId": "def456", "name": "Client Workspace", "role": "member" } ] } ``` With a workspace key it returns the bound workspace: ```json { "keyType": "workspace", "keyPrefix": "cs_a1b2c", "workspaceId": "abc123", "workspaceName": "My Agency", "scopes": ["campaigns:read", "campaigns:write", "leads:read"] } ``` This tool requires no specific scope — any valid key can call it. ## Multi-workspace setups [Section titled “Multi-workspace setups”](#multi-workspace-setups) A **user key** (`csu_`) covers all workspaces you belong to with a single MCP server entry — pass the target `workspaceId` per tool call. **Workspace keys** (`cs_`) belong to exactly one workspace. If you use them to manage multiple workspaces (e.g. one per client), you need a separate API key and MCP server configuration for each: ```json { "mcpServers": { "campaignstack-acme": { "url": "https://mcp.campaignstack.io/mcp", "headers": { "Authorization": "Bearer cs_acme_workspace_key" } }, "campaignstack-globex": { "url": "https://mcp.campaignstack.io/mcp", "headers": { "Authorization": "Bearer cs_globex_workspace_key" } } } } ``` Note Prefer a single user key (`csu_`) for multi-workspace access. Use `campaignstack_whoami` to confirm which workspace(s) a key can reach. ## Server-to-server auth [Section titled “Server-to-server auth”](#server-to-server-auth) For internal service communication, CampaignStack supports a separate authentication method using the `X-Runner-Secret` header with additional context headers (`x-workspace-id`, `x-user-id`). This grants all scopes and is used by the runner service. # Error Handling > Error formats, common errors, and how to handle them. ## Error format [Section titled “Error format”](#error-format) All tool errors return the standard MCP error format: ```json { "content": [{ "type": "text", "text": "Error message here" }], "isError": true } ``` The `text` field contains a plain, human-readable message (not a JSON-encoded string). Errors are **actionable** — they tell you what went wrong and suggest which tool to call next. ## Common errors [Section titled “Common errors”](#common-errors) ### Insufficient permissions [Section titled “Insufficient permissions”](#insufficient-permissions) ```json { "text": "Insufficient permissions: \"campaigns:write\" scope is required to call campaignstack_create_campaign. Use campaignstack_whoami to check your current scopes." } ``` **Fix:** Create a new API key with the required scope, or add the scope to your existing key. ### Not found [Section titled “Not found”](#not-found) ```json { "text": "Campaign not found (id: abc123). Use campaignstack_list_campaigns to find valid IDs." } ``` **Fix:** Use the suggested list tool to find valid IDs. ### Validation error [Section titled “Validation error”](#validation-error) ```json { "text": "Invalid input: workspaceId is required" } ``` **Fix:** Check the tool’s input schema and provide all required fields. All tools use `.strict()` validation — unknown fields are rejected. ### Rate limit exceeded [Section titled “Rate limit exceeded”](#rate-limit-exceeded) ```json { "text": "Rate limit exceeded for campaignstack_craft_message: llm tool limit of 10 requests/minute. Retry after 12s. These limits protect the API from abuse and runaway loops — slow down or batch your calls." } ``` Each API key has per-minute limits, bucketed by tool category. Defaults (override with `MCP_RATE_LIMIT_*_PER_MIN`): | Category | Limit/min | Examples | | --------- | --------- | ---------------------------------------- | | `read` | 120 | list/get tools | | `write` | 30 | create/update/delete tools | | `llm` | 10 | AI craft tools (`campaignstack_craft_*`) | | `overall` | 240 | per-key ceiling across all categories | Internal server-to-server callers are exempt. Current active limits are returned on `GET /health`. **Fix:** Slow down or batch your calls; wait the number of seconds in the message before retrying. ### workspaceId mismatch [Section titled “workspaceId mismatch”](#workspaceid-mismatch) With a workspace key (`cs_`): ```json { "text": "workspaceId mismatch: this API key is bound to a different workspace. Omit workspaceId (it defaults to your key's workspace) or use campaignstack_whoami to inspect your key." } ``` With a user key (`csu_`): ```json { "text": "workspaceId mismatch: you are not a member of this workspace. Use campaignstack_whoami to list your workspaces." } ``` **Fix:** Workspace keys (`cs_`) are bound to one workspace — omit the `workspaceId` argument (it defaults to the key’s workspace), or use a key bound to the workspace you’re targeting. With a user key (`csu_`), pass a `workspaceId` of a workspace you’re a member of — call `campaignstack_whoami` to list them. ### Authentication errors (HTTP only) [Section titled “Authentication errors (HTTP only)”](#authentication-errors-http-only) | Status | Message | Fix | | ------ | --------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------- | | 401 | ”Authentication required. Provide a valid API key in the Authorization header: Bearer csu\_… (user key) or cs\_… (workspace key)“ | Add an `Authorization: Bearer ` header | | 401 | ”Invalid or revoked API key. Create a new key in Settings > API Keys.” | Key was revoked or doesn’t exist — create a new one | ## Cross-referencing pattern [Section titled “Cross-referencing pattern”](#cross-referencing-pattern) CampaignStack errors consistently point to related tools. This helps AI agents self-correct: * **“Use campaignstack\_list\_campaigns to find valid IDs”** — after a not-found error * **“Use campaignstack\_whoami to check your current scopes”** — after a permission error * **Tool descriptions** reference related tools: “Use the returned campaignId values with campaignstack\_get\_campaign, campaignstack\_list\_workflows, or campaignstack\_list\_lead\_lists.” This pattern enables agents to recover from errors without human intervention. ## Input validation [Section titled “Input validation”](#input-validation) All tools enforce strict input validation via Zod schemas: * **Required fields** — missing required fields return clear error messages * **Type checking** — wrong types (string where number expected) are rejected * **Range validation** — numeric limits (e.g., `limit` must be 1-100) are enforced * **Unknown fields** — extra fields not in the schema are rejected (`.strict()` mode) # MCP Server Overview > Architecture and capabilities of the CampaignStack MCP server. CampaignStack exposes its entire platform through the [Model Context Protocol](https://modelcontextprotocol.io/) (MCP). The MCP server provides **200+ tools** across **30 domains**, giving AI agents full programmatic access to campaigns, leads, workflows, content, outreach, inbox, and more. ## Architecture [Section titled “Architecture”](#architecture) ```plaintext AI Agent / MCP Client │ ▼ MCP Server (port 3002) ├── Auth: API key validation + scope check ├── Validation: Zod .strict() input schemas └── Backend: Convex (serverless) └── Runner (LinkedIn automation) ``` The server is hosted at `https://mcp.campaignstack.io/mcp` (Streamable HTTP, API key auth) — see [Transports](/mcp/transports/). ## Tool domains [Section titled “Tool domains”](#tool-domains) | Domain | Tools | Description | | ----------------------- | ----- | ------------------------------------------------------------------------- | | Campaigns | 7 | Create, list, get, update, archive, delete campaigns | | Personas & Phases | 10 | Buyer personas and campaign roadmap phases | | Leads | 7 | Search, get, import CSV, contact info, platform profile linking | | Lead Lists | 8 | Static and query-based lists, add/remove leads | | Companies | 3 | Company lookup and company signals | | ICPs | 6 | ICP CRUD, scoring triggers, score breakdowns | | Enrichment | 3 | Queue leads, priority enrich, enrichment status | | Workflows | 8 | Create, update, trigger, node vocabulary | | Workflow Execution | 6 | Stats, leads at node, activity, retry, requeue | | Workflow Nodes | 4 | Complete, fail, store conversations, escalate | | Workflow Review | 6 | Human-in-the-loop approve/reject/edit | | Split Optimization | 2 | A/B split re-weighting and optimization logs | | Craft | 5 | AI-powered message, note, comment, followup, reply writing | | Content | 17 | Post lifecycle, scheduling, brand voice, analytics, calendar | | Outreach | 5 | Connection request, InMail, like, comment, follow | | Inbox | 6 | Conversations, messages, unread counts | | Signals | 4 | Lead score breakdown, signal weights | | Signal Agents | 10 | Audience agents: CRUD, stats, leads | | Signal Exclusions | 5 | Exclusion lists and opt-outs | | Competitors | 6 | Company/profile monitoring watches | | Topics & Search Watches | 3 | Search topics and saved search watches | | LinkedIn Accounts | 6 | Accounts, connections export, private proxies | | Google Services | 12 | Gmail sending, calendar, email warmup | | Integrations | 7 | Apollo, Instantly, webhook templates | | Automations | 4 | List, toggle, job history | | Workspace | 13 | Members, invitations, settings, branding, playbook | | Metrics & Billing | 4 | Campaign/workspace metrics, credit balance | | Notifications | 6 | List, mark read, preferences | | Search & Diagnostics | 4 | Search URL builder, sourcing diagnosis, post engagement, website analysis | | Admin | 14 | Blog and feature flag management (platform admins only) | ## Design principles [Section titled “Design principles”](#design-principles) * **JSON-only responses** — all tool output is structured JSON, never markdown * **Cross-referenced errors** — error messages suggest which tool to call next * **Strict validation** — Zod `.strict()` rejects unknown input fields * **MCP annotations** — every tool declares `readOnlyHint`, `destructiveHint`, `idempotentHint` * **Actionable descriptions** — tool descriptions reference related tools to help agents chain operations ## Endpoints [Section titled “Endpoints”](#endpoints) | Method | Path | Auth | Purpose | | -------- | --------- | ------ | --------------------------------- | | `GET` | `/health` | None | Server status, version, tool list | | `POST` | `/mcp` | Bearer | MCP JSON-RPC messages | | `GET` | `/mcp` | Bearer | SSE stream for sessions | | `DELETE` | `/mcp` | Bearer | Close session | ## Next steps [Section titled “Next steps”](#next-steps) * [Authentication](/mcp/authentication/) — API keys and scopes * [Transports](/mcp/transports/) — HTTP connection details * [Tool Reference](/mcp/tools/campaigns/) — Full tool documentation # Scopes > Permission matrix — which scopes control which tools. Scopes control which tools an API key can access. Each scope follows the pattern `{domain}:{action}` where action is `read` or `write`. ## Available scopes [Section titled “Available scopes”](#available-scopes) | Scope | Description | | --------------------------- | ------------------------------------------------------------------------------------- | | `workspace:read` | View workspace details and members | | `workspace:write` | Update workspace settings, manage members and invitations | | `campaigns:read` | List and view campaigns | | `campaigns:write` | Create and update campaigns | | `leads:read` | Search and view leads | | `leads:write` | Import leads, queue enrichment | | `lead_lists:read` | List and view lead lists | | `lead_lists:write` | Create lead lists, add leads | | `workflows:read` | List and view workflows | | `workflows:write` | Trigger, update, and manage workflows | | `icps:read` | List and view ICP scores | | `icps:write` | Create, update, delete ICPs and trigger ICP scoring | | `content:read` | List and view content posts, brand voice | | `content:write` | Create, update, schedule, publish content | | `competitors:read` | List and view competitor watches | | `competitors:write` | Create, delete, pause, resume competitor watches | | `accounts:read` | View LinkedIn account health, status, budgets, and ramp-up state | | `accounts:write` | Trigger LinkedIn account operations (e.g. connections export) | | `automations:read` | List scheduled automations and inspect job history | | `automations:write` | Enable and disable scheduled automations | | `signals:read` | List signal agents (audiences), their stats and leads | | `signals:write` | Create, update, pause, resume, delete signal agents | | `inbox:read` | List LinkedIn conversations, messages, and unread counts | | `inbox:write` | Mark conversations read and refresh the inbox | | `integrations:read` | List connected integrations and Apollo import progress | | `integrations:write` | Connect/disconnect Apollo & Instantly, import Apollo lists | | `notifications:read` | List notifications and unread counts | | `notifications:write` | Mark notifications read | | `outreach:write` | Direct LinkedIn outreach actions: connection request, like, comment, follow, InMail | | `google_integrations:read` | View Google account integrations, calendar availability, warmup status | | `google_integrations:write` | Send Gmail, manage calendar events, control email warmup | | `admin:read` | Platform-admin read tools (blog, etc.) — mintable only by platform admins | | `admin:write` | Platform-admin write tools (blog management, etc.) — mintable only by platform admins | ## Wildcard scope [Section titled “Wildcard scope”](#wildcard-scope) The `*` scope grants access to all tools. It’s reserved for internal server-to-server connections — API keys always carry an explicit scope list. ## Scope enforcement [Section titled “Scope enforcement”](#scope-enforcement) Every tool checks scopes before executing. If a key lacks the required scope, the tool returns an error: ```json { "content": [{ "type": "text", "text": "Insufficient permissions: \"campaigns:write\" scope is required to call campaignstack_create_campaign. Use campaignstack_whoami to check your current scopes." }], "isError": true } ``` Note `write` scopes only grant write access — they don’t implicitly include `read`. If you need both, grant both scopes to your API key. ## Scope-to-tool mapping [Section titled “Scope-to-tool mapping”](#scope-to-tool-mapping) Note The tables below show representative tools per domain — they are not exhaustive. Each tool’s authoritative required scope is listed on its [tool reference](/mcp/tools/campaigns/) page (one page per domain), generated directly from the server. A few tools require a scope from a different domain than their name suggests — e.g. the AI craft tools (`campaignstack_craft_*`) require `workflows:write`, LinkedIn account tools require `accounts:read`/`accounts:write`, and automation tools require `automations:read`/`automations:write`. Always check the tool reference page when in doubt. ### campaigns [Section titled “campaigns”](#campaigns) | Tool | Scope | | ------------------------------- | ----------------- | | `campaignstack_list_campaigns` | `campaigns:read` | | `campaignstack_get_campaign` | `campaigns:read` | | `campaignstack_create_campaign` | `campaigns:write` | | `campaignstack_update_campaign` | `campaigns:write` | ### leads [Section titled “leads”](#leads) | Tool | Scope | | -------------------------------- | ------------- | | `campaignstack_search_leads` | `leads:read` | | `campaignstack_get_lead` | `leads:read` | | `campaignstack_import_leads_csv` | `leads:write` | | `campaignstack_queue_leads` | `leads:write` | | `campaignstack_build_search_url` | `leads:read` | ### lead\_lists [Section titled “lead\_lists”](#lead_lists) | Tool | Scope | | --------------------------------- | ------------------ | | `campaignstack_list_lead_lists` | `lead_lists:read` | | `campaignstack_get_lead_list` | `lead_lists:read` | | `campaignstack_create_lead_list` | `lead_lists:write` | | `campaignstack_add_leads_to_list` | `lead_lists:write` | ### workflows [Section titled “workflows”](#workflows) | Tool | Scope | | -------------------------------------- | ----------------- | | `campaignstack_list_workflows` | `workflows:read` | | `campaignstack_get_workflow` | `workflows:read` | | `campaignstack_update_workflow_status` | `workflows:write` | | `campaignstack_trigger_workflow` | `workflows:write` | ### icps [Section titled “icps”](#icps) | Tool | Scope | | ----------------------------------- | ------------ | | `campaignstack_list_icps` | `icps:read` | | `campaignstack_get_icp_scores` | `icps:read` | | `campaignstack_trigger_icp_scoring` | `icps:write` | ### content [Section titled “content”](#content) | Tool | Scope | | ------------------------------------------- | --------------- | | `campaignstack_list_content_posts` | `content:read` | | `campaignstack_get_content_post` | `content:read` | | `campaignstack_get_workspace_voice` | `content:read` | | `campaignstack_create_content_post` | `content:write` | | `campaignstack_update_content_post` | `content:write` | | `campaignstack_delete_content_post` | `content:write` | | `campaignstack_duplicate_content_post` | `content:write` | | `campaignstack_schedule_content_post` | `content:write` | | `campaignstack_submit_content_for_approval` | `content:write` | | `campaignstack_approve_content_post` | `content:write` | | `campaignstack_reject_content_post` | `content:write` | | `campaignstack_unschedule_content_post` | `content:write` | | `campaignstack_retry_content_post` | `content:write` | | `campaignstack_update_brand_identity` | `content:write` | ## No-scope tools [Section titled “No-scope tools”](#no-scope-tools) `campaignstack_whoami` requires no specific scope — any valid API key can call it. # Automations > View and manage scheduled campaign automations — enable, disable, and inspect job history. View and manage scheduled campaign automations — enable, disable, and inspect job history. ## Tools (3) [Section titled “Tools (3)”](#tools-3) ### list\_automations [Section titled “list\_automations”](#list_automations) **Full name:** `campaignstack_list_automations` automations:read Read-only Idempotent List scheduled automations for a campaign. Shows automation name, schedule, enabled status, and last run outcome. #### Parameters [Section titled “Parameters”](#parameters) | Name | Type | Required | Default | Description | | ------------ | ------ | -------- | ------- | --------------- | | `campaignId` | string | Yes | — | (min length: 1) | *** ### toggle\_automation [Section titled “toggle\_automation”](#toggle_automation) **Full name:** `campaignstack_toggle_automation` automations:write Toggle a scheduled automation on or off. Returns the new enabled state. #### Parameters [Section titled “Parameters”](#parameters-1) | Name | Type | Required | Default | Description | | -------------- | ------ | -------- | ------- | --------------- | | `automationId` | string | Yes | — | (min length: 1) | *** ### get\_automation\_jobs [Section titled “get\_automation\_jobs”](#get_automation_jobs) **Full name:** `campaignstack_get_automation_jobs` automations:read Read-only Idempotent Get recent job execution history for a scheduled automation. Returns the last 10 jobs with status, timestamps, and results. #### Parameters [Section titled “Parameters”](#parameters-2) | Name | Type | Required | Default | Description | | -------------- | ------ | -------- | ------- | --------------- | | `automationId` | string | Yes | — | (min length: 1) | *** # Calendar > Google Calendar tools — check availability, find free slots, and create, update, or delete events on connected Google accounts. Google Calendar tools — check availability, find free slots, and create, update, or delete events on connected Google accounts. ## Tools (5) [Section titled “Tools (5)”](#tools-5) ### get\_free\_slots [Section titled “get\_free\_slots”](#get_free_slots) **Full name:** `campaignstack_get_free_slots` google\_integrations:read Read-only Idempotent Find the next available time slots in the user’s Google Calendar. Fetches events for up to 14 days from fromDate, then returns the first N gaps that fit the requested duration. When businessHoursOnly is true (default), slots are restricted to the account’s configured scheduling hours (schedulingStartHour/schedulingEndHour, default 9 AM–6 PM) in the Google account’s timezone (auto-detected from the calendar on connect; falls back to UTC). Requires a Google account to be connected and linked to the workspace. If googleAccountId is omitted, uses the first connected Google account. #### Parameters [Section titled “Parameters”](#parameters) | Name | Type | Required | Default | Description | | ------------------- | ------- | -------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------- | | `workspaceId` | string | No | — | Workspace ID (defaults to the bound workspace) | | `googleAccountId` | string | No | — | Google account ID to use. Defaults to the first connected Google account linked to the workspace. | | `fromDate` | string | Yes | — | Start of the search window (ISO 8601 string, e.g. ‘2026-06-17T00:00:00Z’). Slots earlier than this date are excluded. | | `durationMinutes` | integer | No | `30` | Duration of each slot in minutes (default: 30) | | `count` | integer | No | `3` | Number of free slots to return (default: 3) | | `businessHoursOnly` | boolean | No | `true` | If true (default), only return slots within business hours (9 AM–6 PM in the Google account’s timezone, defaults to UTC if not set) | *** ### check\_calendar\_availability [Section titled “check\_calendar\_availability”](#check_calendar_availability) **Full name:** `campaignstack_check_calendar_availability` google\_integrations:read Read-only Idempotent Check whether a specific time slot is available in the user’s Google Calendar. Returns { available, conflictingEvents } where conflictingEvents is a list of events overlapping the requested range (each with summary, start, end). Requires a Google account to be connected and linked to the workspace. If googleAccountId is omitted, uses the first connected Google account linked to the workspace. #### Parameters [Section titled “Parameters”](#parameters-1) | Name | Type | Required | Default | Description | | ----------------- | ------ | -------- | ------- | ------------------------------------------------------------------------------------------------- | | `workspaceId` | string | No | — | Workspace ID (defaults to the bound workspace) | | `googleAccountId` | string | No | — | Google account ID to use. Defaults to the first connected Google account linked to the workspace. | | `startTime` | string | Yes | — | Start of the time range to check (ISO 8601 string, e.g. ‘2026-06-17T14:00:00Z’) | | `endTime` | string | Yes | — | End of the time range to check (ISO 8601 string, e.g. ‘2026-06-17T15:00:00Z’) | *** ### create\_calendar\_event [Section titled “create\_calendar\_event”](#create_calendar_event) **Full name:** `campaignstack_create_calendar_event` google\_integrations:write Create a Google Calendar event and optionally invite attendees. When attendeeEmails are provided, Google sends invite emails automatically (sendUpdates=all). Requires a Google account to be connected and linked to the workspace. If googleAccountId is omitted, uses the first connected Google account linked to the workspace. Returns { eventId, htmlLink } on success. #### Parameters [Section titled “Parameters”](#parameters-2) | Name | Type | Required | Default | Description | | ----------------- | --------- | -------- | ------- | ------------------------------------------------------------------------------------------------------------ | | `workspaceId` | string | No | — | Workspace ID (defaults to the bound workspace) | | `googleAccountId` | string | No | — | Google account ID to use. Defaults to the first connected Google account linked to the workspace. | | `summary` | string | Yes | — | Event title (min length: 1) | | `startTime` | string | Yes | — | Event start time (ISO 8601, e.g. ‘2026-06-17T14:00:00Z’) | | `endTime` | string | Yes | — | Event end time (ISO 8601, e.g. ‘2026-06-17T15:00:00Z’) | | `description` | string | No | — | Optional event description / agenda | | `attendeeEmails` | string\[] | No | — | Optional list of attendee email addresses. Google Calendar sends invites automatically when sendUpdates=all. | | `location` | string | No | — | Optional event location (address, room name, or video link) | *** ### update\_calendar\_event [Section titled “update\_calendar\_event”](#update_calendar_event) **Full name:** `campaignstack_update_calendar_event` google\_integrations:write Update (reschedule) an existing Google Calendar event. All fields are optional — only the provided fields are changed (PATCH semantics). When attendeeEmails are provided, they replace the existing attendee list and Google sends update notifications automatically (sendUpdates=all). Requires a Google account to be connected and linked to the workspace. If googleAccountId is omitted, uses the first connected Google account linked to the workspace. Returns { eventId, htmlLink } on success. #### Parameters [Section titled “Parameters”](#parameters-3) | Name | Type | Required | Default | Description | | ----------------- | --------- | -------- | ------- | ----------------------------------------------------------------------------------------------------------------------------- | | `workspaceId` | string | No | — | Workspace ID (defaults to the bound workspace) | | `googleAccountId` | string | No | — | Google account ID to use. Defaults to the first connected Google account linked to the workspace. | | `eventId` | string | Yes | — | Google Calendar event ID to update (min length: 1) | | `summary` | string | No | — | New event title | | `startTime` | string | No | — | New event start time (ISO 8601, e.g. ‘2026-06-17T14:00:00Z’) | | `endTime` | string | No | — | New event end time (ISO 8601, e.g. ‘2026-06-17T15:00:00Z’) | | `description` | string | No | — | New event description / agenda | | `attendeeEmails` | string\[] | No | — | New list of attendee email addresses (replaces existing attendees). Google Calendar sends update notifications automatically. | | `location` | string | No | — | New event location (address, room name, or video link) | *** ### delete\_calendar\_event [Section titled “delete\_calendar\_event”](#delete_calendar_event) **Full name:** `campaignstack_delete_calendar_event` google\_integrations:write Destructive Idempotent Delete (cancel) a Google Calendar event. By default, cancellation notifications are sent to all attendees (sendNotifications=true). Requires a Google account to be connected and linked to the workspace. If googleAccountId is omitted, uses the first connected Google account linked to the workspace. Returns { deleted: true } on success. #### Parameters [Section titled “Parameters”](#parameters-4) | Name | Type | Required | Default | Description | | ------------------- | ------- | -------- | ------- | ------------------------------------------------------------------------------------------------- | | `workspaceId` | string | No | — | Workspace ID (defaults to the bound workspace) | | `googleAccountId` | string | No | — | Google account ID to use. Defaults to the first connected Google account linked to the workspace. | | `eventId` | string | Yes | — | Google Calendar event ID to delete (min length: 1) | | `sendNotifications` | boolean | No | `true` | If true (default), Google sends cancellation notifications to all attendees. | *** # Campaigns > Create, list, get, update, and manage the lifecycle of outreach campaigns. Create, list, get, update, and manage the lifecycle of outreach campaigns. ## Tools (7) [Section titled “Tools (7)”](#tools-7) ### list\_campaigns [Section titled “list\_campaigns”](#list_campaigns) **Full name:** `campaignstack_list_campaigns` campaigns:read Read-only Idempotent List campaigns in a workspace. Supports cursor-based pagination. Use the returned campaignId values with campaignstack\_get\_campaign, campaignstack\_list\_workflows, or campaignstack\_list\_lead\_lists. #### Parameters [Section titled “Parameters”](#parameters) | Name | Type | Required | Default | Description | | ------------- | ------- | -------- | ------- | ----------------------------------- | | `workspaceId` | string | No | — | Defaults to the API key’s workspace | | `limit` | integer | No | `20` | (min: 1, max: 100) | | `cursor` | string | No | — | | *** ### get\_campaign [Section titled “get\_campaign”](#get_campaign) **Full name:** `campaignstack_get_campaign` campaigns:read Read-only Idempotent Get a campaign by ID with lead list count, workflow count, and ICP summary. If the campaign is not found, use campaignstack\_list\_campaigns to find valid campaign IDs. #### Parameters [Section titled “Parameters”](#parameters-1) | Name | Type | Required | Default | Description | | ------------ | ------ | -------- | ------- | --------------- | | `campaignId` | string | Yes | — | (min length: 1) | *** ### create\_campaign [Section titled “create\_campaign”](#create_campaign) **Full name:** `campaignstack_create_campaign` campaigns:write Create a new campaign in a workspace. Returns the new campaign ID and title. Creates a new campaign ready for configuration. #### Parameters [Section titled “Parameters”](#parameters-2) | Name | Type | Required | Default | Description | | ------------- | ------ | -------- | ------- | ----------------------------------- | | `workspaceId` | string | No | — | Defaults to the API key’s workspace | | `title` | string | Yes | — | (min length: 1) | | `description` | string | No | — | | | `goal` | string | Yes | — | (min length: 1) | *** ### update\_campaign [Section titled “update\_campaign”](#update_campaign) **Full name:** `campaignstack_update_campaign` campaigns:write Idempotent Update a campaign’s title or description. Only provided fields are updated. Returns the updated campaign object. #### Parameters [Section titled “Parameters”](#parameters-3) | Name | Type | Required | Default | Description | | ------------- | ------ | -------- | ------- | --------------- | | `campaignId` | string | Yes | — | (min length: 1) | | `title` | string | No | — | (min length: 1) | | `description` | string | No | — | | *** ### archive\_campaign [Section titled “archive\_campaign”](#archive_campaign) **Full name:** `campaignstack_archive_campaign` campaigns:write Idempotent Archive a campaign (soft archive). Disables all search watch automations. The campaign and its data are preserved but removed from active views. Use campaignstack\_unarchive\_campaign to restore it. If the campaign is not found, use campaignstack\_list\_campaigns to find valid IDs. #### Parameters [Section titled “Parameters”](#parameters-4) | Name | Type | Required | Default | Description | | ------------ | ------ | -------- | ------- | --------------- | | `campaignId` | string | Yes | — | (min length: 1) | *** ### unarchive\_campaign [Section titled “unarchive\_campaign”](#unarchive_campaign) **Full name:** `campaignstack_unarchive_campaign` campaigns:write Idempotent Unarchive a campaign, restoring it to active status. Re-evaluates search watches. If the campaign is not found, use campaignstack\_list\_campaigns to find valid IDs. #### Parameters [Section titled “Parameters”](#parameters-5) | Name | Type | Required | Default | Description | | ------------ | ------ | -------- | ------- | --------------- | | `campaignId` | string | Yes | — | (min length: 1) | *** ### delete\_campaign [Section titled “delete\_campaign”](#delete_campaign) **Full name:** `campaignstack_delete_campaign` campaigns:write Destructive PERMANENTLY delete a campaign and cascade-delete all associated data: workflows (nodes, edges, lead contexts, webhook tokens), lead lists and entries, ICPs and lead scores, personas, phases, metrics, signal weights, topics, conversations, search cursors, scheduled automations, and enrichment queue items. This action is irreversible. Use campaignstack\_archive\_campaign instead to preserve data. If the campaign is not found, use campaignstack\_list\_campaigns to find valid IDs. #### Parameters [Section titled “Parameters”](#parameters-6) | Name | Type | Required | Default | Description | | ------------ | ------ | -------- | ------- | --------------- | | `campaignId` | string | Yes | — | (min length: 1) | *** # Companies > Look up company records — get a single company by ID or list the companies linked to a workspace. Look up company records — get a single company by ID or list the companies linked to a workspace. ## Tools (2) [Section titled “Tools (2)”](#tools-2) ### get\_company [Section titled “get\_company”](#get_company) **Full name:** `campaignstack_get_company` leads:read Read-only Idempotent Get a company by ID. Returns the global company record including industry, employee range, and contact info. Platform presence (LinkedIn URL, follower/staff counts, tagline) is under the `profiles` key (e.g. profiles.linkedin.url); top-level LinkedIn fields are legacy aliases. If the company is not found, use campaignstack\_list\_companies to find valid company IDs. #### Parameters [Section titled “Parameters”](#parameters) | Name | Type | Required | Default | Description | | ----------- | ------ | -------- | ------- | --------------- | | `companyId` | string | Yes | — | (min length: 1) | *** ### list\_companies [Section titled “list\_companies”](#list_companies) **Full name:** `campaignstack_list_companies` leads:read Read-only Idempotent List companies linked to a workspace. Returns company records with workspace metadata (tags, notes, addedAt). Supports cursor-based pagination. Use the returned companyId values with campaignstack\_get\_company. #### Parameters [Section titled “Parameters”](#parameters-1) | Name | Type | Required | Default | Description | | ------------- | ------- | -------- | ------- | ----------------------------------- | | `workspaceId` | string | No | — | Defaults to the API key’s workspace | | `limit` | integer | No | `20` | (min: 1, max: 100) | | `cursor` | string | No | — | | *** # Competitors > Monitor competitor LinkedIn activity and engagement. Monitor competitor LinkedIn activity and engagement. ## Tools (6) [Section titled “Tools (6)”](#tools-6) ### create\_competitor\_watch [Section titled “create\_competitor\_watch”](#create_competitor_watch) **Full name:** `campaignstack_create_competitor_watch` competitors:write Creates a new competitor watch to monitor a LinkedIn company page or influencer profile. Automatically deduplicates by normalized LinkedIn URL within the workspace. Use campaignstack\_list\_competitor\_watches to see existing watches. #### Parameters [Section titled “Parameters”](#parameters) | Name | Type | Required | Default | Description | | ------------- | ------------------------- | -------- | ------- | -------------------------------------------------------------------------------------- | | `workspaceId` | string | No | — | Defaults to the API key’s workspace | | `profileUrl` | string | Yes | — | (min length: 1) | | `name` | string | Yes | — | (min length: 1) | | `entityType` | `company` \| `influencer` | Yes | — | | | `accountIds` | string\[] | No | — | Optional LinkedIn account IDs to use for scraping (defaults to all workspace accounts) | *** ### list\_competitor\_watches [Section titled “list\_competitor\_watches”](#list_competitor_watches) **Full name:** `campaignstack_list_competitor_watches` competitors:read Read-only Idempotent Lists competitor watches for a workspace. Optionally filter by status (active/paused). Use campaignstack\_get\_competitor\_watch to see post details for a specific watch. #### Parameters [Section titled “Parameters”](#parameters-1) | Name | Type | Required | Default | Description | | ------------- | -------------------- | -------- | ------- | ------------------------------------------- | | `workspaceId` | string | No | — | Defaults to the API key’s workspace | | `status` | `active` \| `paused` | No | — | Filter by status. Omit to list all watches. | *** ### get\_competitor\_watch [Section titled “get\_competitor\_watch”](#get_competitor_watch) **Full name:** `campaignstack_get_competitor_watch` competitors:read Read-only Idempotent Returns a competitor watch with post count and the 5 latest posts. Use campaignstack\_list\_competitor\_watches to find valid watch IDs. #### Parameters [Section titled “Parameters”](#parameters-2) | Name | Type | Required | Default | Description | | --------- | ------ | -------- | ------- | --------------- | | `watchId` | string | Yes | — | (min length: 1) | *** ### delete\_competitor\_watch [Section titled “delete\_competitor\_watch”](#delete_competitor_watch) **Full name:** `campaignstack_delete_competitor_watch` competitors:write Destructive Deletes a competitor watch and disables its associated automations. This action is irreversible. Use campaignstack\_pause\_competitor\_watch to temporarily stop monitoring instead. #### Parameters [Section titled “Parameters”](#parameters-3) | Name | Type | Required | Default | Description | | --------- | ------ | -------- | ------- | --------------- | | `watchId` | string | Yes | — | (min length: 1) | *** ### pause\_competitor\_watch [Section titled “pause\_competitor\_watch”](#pause_competitor_watch) **Full name:** `campaignstack_pause_competitor_watch` competitors:write Idempotent Pauses a competitor watch and disables its associated automations. Use campaignstack\_resume\_competitor\_watch to resume monitoring. #### Parameters [Section titled “Parameters”](#parameters-4) | Name | Type | Required | Default | Description | | --------- | ------ | -------- | ------- | --------------- | | `watchId` | string | Yes | — | (min length: 1) | *** ### resume\_competitor\_watch [Section titled “resume\_competitor\_watch”](#resume_competitor_watch) **Full name:** `campaignstack_resume_competitor_watch` competitors:write Idempotent Resumes a paused competitor watch and re-enables its associated automations. Use campaignstack\_pause\_competitor\_watch to pause monitoring. #### Parameters [Section titled “Parameters”](#parameters-5) | Name | Type | Required | Default | Description | | --------- | ------ | -------- | ------- | --------------- | | `watchId` | string | Yes | — | (min length: 1) | *** # Content Posts > Create, schedule, and publish LinkedIn content through its full lifecycle. Create, schedule, and publish LinkedIn content through its full lifecycle. ## Tools (17) [Section titled “Tools (17)”](#tools-17) ### create\_content\_post [Section titled “create\_content\_post”](#create_content_post) **Full name:** `campaignstack_create_content_post` content:write Creates a draft content post for a LinkedIn account. Returns the new post ID. Use campaignstack\_get\_content\_post to retrieve the full post. postType is auto-inferred from media if not provided (no media = text, image/\* = image, video/\* = video, pdf/pptx = document). Optional tags array for categorization (system tags: ai-generated, weekly-suggestion, on-demand, competitor-inspired; or freeform user tags). #### Parameters [Section titled “Parameters”](#parameters) | Name | Type | Required | Default | Description | | ----------------- | ------------------------------------------ | -------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `workspaceId` | string | No | — | Defaults to the API key’s workspace | | `authorAccountId` | string | Yes | — | (min length: 1) | | `platform` | `linkedin` | Yes | — | | | `body` | string | Yes | — | (min length: 1) | | `mediaIds` | string\[] | No | `[]` | | | `tags` | string\[] | No | — | | | `postType` | `text` \| `image` \| `video` \| `document` | No | — | | | `scheduledAt` | integer | No | — | Schedule the post at this exact time after creating it. | | `autoSchedule` | boolean | No | — | Create the post and auto-place it into the next valid posting slot (respects the account’s daily LinkedIn limit). Use this to queue many posts without picking times. | *** ### update\_content\_post [Section titled “update\_content\_post”](#update_content_post) **Full name:** `campaignstack_update_content_post` content:write Idempotent Updates a draft content post. Only posts in ‘draft’ or ‘scheduled’ status can be edited. Pass only the fields you want to change. Supports updating tags (replaces entire array). Use campaignstack\_get\_content\_post to check current status before updating. #### Parameters [Section titled “Parameters”](#parameters-1) | Name | Type | Required | Default | Description | | --------------- | ------------------------------------------ | -------- | ------- | --------------- | | `contentPostId` | string | Yes | — | (min length: 1) | | `body` | string | No | — | (min length: 1) | | `mediaIds` | string\[] | No | — | | | `tags` | string\[] | No | — | | | `postType` | `text` \| `image` \| `video` \| `document` | No | — | | *** ### list\_content\_posts [Section titled “list\_content\_posts”](#list_content_posts) **Full name:** `campaignstack_list_content_posts` content:read Read-only Idempotent Lists content posts for a workspace with optional filtering by status, platform, and tags. Tags filter uses OR logic (posts matching ANY of the provided tags are returned). Returns up to ‘limit’ posts (default 20, max 100) ordered by most recent first. Supports cursor-based pagination: pass the response’s nextCursor back as ‘cursor’ to fetch the next page. Use campaignstack\_get\_content\_post to retrieve full details including media URLs. #### Parameters [Section titled “Parameters”](#parameters-2) | Name | Type | Required | Default | Description | | ------------- | ----------------------------------------------------------------------------------------------------- | -------- | ------- | -------------------------------------------------------------- | | `workspaceId` | string | No | — | Defaults to the API key’s workspace | | `status` | `draft` \| `pending_approval` \| `scheduled` \| `publishing` \| `published` \| `failed` \| `archived` | No | — | | | `platform` | `linkedin` | No | — | | | `tags` | string\[] | No | — | | | `limit` | integer | No | `20` | (min: 1, max: 100) | | `cursor` | string | No | — | Opaque pagination cursor from a previous response’s nextCursor | *** ### get\_content\_post [Section titled “get\_content\_post”](#get_content_post) **Full name:** `campaignstack_get_content_post` content:read Read-only Idempotent Returns a single content post with its LinkedIn extension data and media URLs. Use campaignstack\_list\_content\_posts to find valid post IDs. #### Parameters [Section titled “Parameters”](#parameters-3) | Name | Type | Required | Default | Description | | --------------- | ------ | -------- | ------- | --------------- | | `contentPostId` | string | Yes | — | (min length: 1) | *** ### delete\_content\_post [Section titled “delete\_content\_post”](#delete_content_post) **Full name:** `campaignstack_delete_content_post` content:write Destructive Deletes a content post. Only posts in ‘draft’ or ‘failed’ status can be deleted. Use campaignstack\_get\_content\_post to check status before deleting. #### Parameters [Section titled “Parameters”](#parameters-4) | Name | Type | Required | Default | Description | | --------------- | ------ | -------- | ------- | --------------- | | `contentPostId` | string | Yes | — | (min length: 1) | *** ### duplicate\_content\_post [Section titled “duplicate\_content\_post”](#duplicate_content_post) **Full name:** `campaignstack_duplicate_content_post` content:write Duplicates an existing content post as a new draft, copying body, platform, author, and media. All lifecycle fields are cleared (no schedule, no approval). Returns the new post ID. #### Parameters [Section titled “Parameters”](#parameters-5) | Name | Type | Required | Default | Description | | --------------- | ------ | -------- | ------- | --------------- | | `contentPostId` | string | Yes | — | (min length: 1) | *** ### schedule\_content\_post [Section titled “schedule\_content\_post”](#schedule_content_post) **Full name:** `campaignstack_schedule_content_post` content:write Idempotent Schedules a draft content post for publishing (skips approval). Pass scheduledAt (Unix ms, in the future) for an exact time, or set autoSchedule:true (or omit scheduledAt) to auto-place it into the next valid slot that respects the account’s daily LinkedIn posting limit. The response includes the resolved scheduledAt. Use campaignstack\_get\_content\_post to verify the post is in ‘draft’ status first. #### Parameters [Section titled “Parameters”](#parameters-6) | Name | Type | Required | Default | Description | | --------------- | ------- | -------- | ------- | ---------------------------------------------------------------------------------------------------------- | | `contentPostId` | string | Yes | — | (min length: 1) | | `scheduledAt` | integer | No | — | Exact publish time. Omit (or set autoSchedule) for the next valid slot. | | `autoSchedule` | boolean | No | — | Auto-place into the next valid posting slot instead of an exact time. Default when scheduledAt is omitted. | *** ### submit\_content\_for\_approval [Section titled “submit\_content\_for\_approval”](#submit_content_for_approval) **Full name:** `campaignstack_submit_content_for_approval` content:write Idempotent Submits a draft content post for approval, moving it to ‘pending\_approval’ status. The post body must not be empty. Use campaignstack\_approve\_content\_post or campaignstack\_reject\_content\_post to continue the workflow. #### Parameters [Section titled “Parameters”](#parameters-7) | Name | Type | Required | Default | Description | | --------------- | ------ | -------- | ------- | --------------- | | `contentPostId` | string | Yes | — | (min length: 1) | *** ### approve\_content\_post [Section titled “approve\_content\_post”](#approve_content_post) **Full name:** `campaignstack_approve_content_post` content:write Idempotent Approves a content post that is ‘pending\_approval’ and schedules it for publishing. Pass scheduledAt (Unix ms) for an exact time, or set autoSchedule:true (or omit scheduledAt) to auto-place it into the next valid slot. The response includes the resolved scheduledAt. Use campaignstack\_reject\_content\_post to send it back to draft instead. #### Parameters [Section titled “Parameters”](#parameters-8) | Name | Type | Required | Default | Description | | --------------- | ------- | -------- | ------- | ---------------------------------------------------------------------------------------------------------- | | `contentPostId` | string | Yes | — | (min length: 1) | | `scheduledAt` | integer | No | — | Exact publish time. Omit (or set autoSchedule) for the next valid slot. | | `autoSchedule` | boolean | No | — | Auto-place into the next valid posting slot instead of an exact time. Default when scheduledAt is omitted. | *** ### reject\_content\_post [Section titled “reject\_content\_post”](#reject_content_post) **Full name:** `campaignstack_reject_content_post` content:write Rejects a ‘pending\_approval’ content post back to ‘draft’ status with a reason. Increments the revision count (max 10 revisions). Use campaignstack\_update\_content\_post to edit the post, then campaignstack\_submit\_content\_for\_approval to resubmit. #### Parameters [Section titled “Parameters”](#parameters-9) | Name | Type | Required | Default | Description | | --------------- | ------ | -------- | ------- | --------------- | | `contentPostId` | string | Yes | — | (min length: 1) | | `reason` | string | Yes | — | (min length: 1) | *** ### unschedule\_content\_post [Section titled “unschedule\_content\_post”](#unschedule_content_post) **Full name:** `campaignstack_unschedule_content_post` content:write Idempotent Moves a ‘scheduled’ content post back to ‘draft’ status, clearing the scheduled time. Use campaignstack\_schedule\_content\_post to reschedule it. #### Parameters [Section titled “Parameters”](#parameters-10) | Name | Type | Required | Default | Description | | --------------- | ------ | -------- | ------- | --------------- | | `contentPostId` | string | Yes | — | (min length: 1) | *** ### retry\_content\_post [Section titled “retry\_content\_post”](#retry_content_post) **Full name:** `campaignstack_retry_content_post` content:write Idempotent Moves a ‘failed’ content post back to ‘draft’ status, clearing the failure reason. Use campaignstack\_get\_content\_post to check the failureReason before retrying. After retrying, edit the post if needed, then schedule or submit for approval again. #### Parameters [Section titled “Parameters”](#parameters-11) | Name | Type | Required | Default | Description | | --------------- | ------ | -------- | ------- | --------------- | | `contentPostId` | string | Yes | — | (min length: 1) | *** ### get\_workspace\_voice [Section titled “get\_workspace\_voice”](#get_workspace_voice) **Full name:** `campaignstack_get_workspace_voice` content:read Read-only Idempotent Returns the brand voice & tone guide for a workspace’s default LinkedIn account. Use this to understand the writing style before generating content drafts. Returns null defaultAccountVoice if not configured. #### Parameters [Section titled “Parameters”](#parameters-12) | Name | Type | Required | Default | Description | | ------------- | ------ | -------- | ------- | ----------------------------------- | | `workspaceId` | string | No | — | Defaults to the API key’s workspace | *** ### update\_brand\_identity [Section titled “update\_brand\_identity”](#update_brand_identity) **Full name:** `campaignstack_update_brand_identity` content:write Idempotent Updates the brand identity for a workspace’s default account voice. Supports updating voice guide text and visual identity (colors, font style, style descriptors). Pass only the fields you want to change. Use campaignstack\_get\_workspace\_voice to check current values first. #### Parameters [Section titled “Parameters”](#parameters-13) | Name | Type | Required | Default | Description | | ------------------ | ------ | -------- | ------- | -------------------------------------------------------- | | `workspaceId` | string | No | — | Defaults to the API key’s workspace | | `voiceGuide` | string | No | — | | | `primaryColor` | string | No | — | Primary brand color as hex (e.g. #1a2b3c) | | `secondaryColor` | string | No | — | Secondary brand color as hex (e.g. #4d5e6f) | | `accentColor` | string | No | — | Accent brand color as hex (e.g. #ff6b35) | | `fontStyle` | string | No | — | Font style description (e.g. bold sans-serif, geometric) | | `styleDescriptors` | string | No | — | Style keywords (e.g. clean, minimal, corporate) | *** ### get\_content\_analytics [Section titled “get\_content\_analytics”](#get_content_analytics) **Full name:** `campaignstack_get_content_analytics` content:read Read-only Idempotent Get analytics for a workspace’s own published LinkedIn content. Returns engagement metrics (reactions, comments, reposts), trend vs previous period, and top-performing posts. Use fromMs/toMs as Unix timestamps (milliseconds) to define the date range. #### Parameters [Section titled “Parameters”](#parameters-14) | Name | Type | Required | Default | Description | | ------------- | ------- | -------- | ------- | ----------------------------------- | | `workspaceId` | string | No | — | Defaults to the API key’s workspace | | `fromMs` | integer | Yes | — | | | `toMs` | integer | Yes | — | | *** ### list\_content\_calendar [Section titled “list\_content\_calendar”](#list_content_calendar) **Full name:** `campaignstack_list_content_calendar` content:read Read-only Idempotent List content posts in a date range for calendar view. Returns posts that are scheduled or published within the given timeframe. Timestamps are Unix milliseconds. Use with campaignstack\_get\_content\_post for full post details. #### Parameters [Section titled “Parameters”](#parameters-15) | Name | Type | Required | Default | Description | | ------------- | ------- | -------- | ------- | ----------------------------------- | | `workspaceId` | string | No | — | Defaults to the API key’s workspace | | `startMs` | integer | Yes | — | | | `endMs` | integer | Yes | — | | *** ### list\_ai\_suggestions [Section titled “list\_ai\_suggestions”](#list_ai_suggestions) **Full name:** `campaignstack_list_ai_suggestions` content:read Read-only Idempotent List the latest AI-generated content post suggestions (draft posts tagged ‘ai-generated’) for a workspace. Returns up to 3 of the most recent AI drafts with author info and LinkedIn extension data. Use campaignstack\_update\_content\_post to edit a suggestion, then campaignstack\_schedule\_content\_post or campaignstack\_submit\_content\_for\_approval to publish it. #### Parameters [Section titled “Parameters”](#parameters-16) | Name | Type | Required | Default | Description | | ------------- | ------ | -------- | ------- | ----------------------------------- | | `workspaceId` | string | No | — | Defaults to the API key’s workspace | *** # AI Craft > AI-powered message, note, comment, and follow-up writing with brand voice. AI-powered message, note, comment, and follow-up writing with brand voice. ## Tools (5) [Section titled “Tools (5)”](#tools-5) ### craft\_message [Section titled “craft\_message”](#craft_message) **Full name:** `campaignstack_craft_message` workflows:write Read-only Generate a personalized LinkedIn first message using full campaign context. Fetches lead profile, campaign goal, ICP, and persona data from Convex, then uses AI to craft a tailored message. Use only for first messages (no prior conversation). For follow-ups with conversation history, use campaignstack\_craft\_followup instead. *** ### craft\_note [Section titled “craft\_note”](#craft_note) **Full name:** `campaignstack_craft_note` workflows:write Read-only Generate a personalized LinkedIn connection note (max 300 characters). Fetches lead profile, campaign goal, ICP, and persona data from Convex, then uses AI to craft a short, compelling connection note. Use for connection requests. For first messages after connecting, use campaignstack\_craft\_message. *** ### craft\_comment [Section titled “craft\_comment”](#craft_comment) **Full name:** `campaignstack_craft_comment` workflows:write Read-only Generate a relevant comment for a LinkedIn post using campaign context. Accepts the post content the agent captured from the browser, plus lead and campaign context. Uses AI to craft a thoughtful comment that builds rapport. Use for engaging with a lead’s LinkedIn posts before or after connecting. *** ### craft\_followup [Section titled “craft\_followup”](#craft_followup) **Full name:** `campaignstack_craft_followup` workflows:write Generate a contextual follow-up message that accounts for the full conversation history. Compares agent-provided messages with stored history to identify new messages, stores any new messages found, then uses AI to craft a reply. Also detects lead intent (interested, not\_interested, asking\_for\_info, scheduling\_call, other). Use for any message where prior conversation exists. For first messages, use campaignstack\_craft\_message. *** ### reply\_to\_comment [Section titled “reply\_to\_comment”](#reply_to_comment) **Full name:** `campaignstack_reply_to_comment` workflows:write Read-only Generate a contextual threaded reply for a LinkedIn comment. Fetches lead profile, campaign context, and ICP data from Convex, then uses AI to craft a relevant, conversational reply. When `addDmHint` is true (set when a DM is also being sent), the reply includes a ‘Sent you a DM!’ nudge. After calling this tool, the agent navigates to the post, finds the comment, clicks Reply, and types the returned `replyText` using browser\_\* tools. *** # Email (Gmail) > Send emails through connected Google accounts — gated by the per-account daily send budget and warming system — and configure Google App Passwords. Send emails through connected Google accounts — gated by the per-account daily send budget and warming system — and configure Google App Passwords. ## Tools (2) [Section titled “Tools (2)”](#tools-2) ### send\_gmail [Section titled “send\_gmail”](#send_gmail) **Full name:** `campaignstack_send_gmail` google\_integrations:write Send an email via Gmail using a connected Google account. Requires a Google account to be connected and linked to the workspace — use the Google integrations settings in the app to connect one first. If googleAccountId is omitted, uses the first connected Google account linked to the workspace. Returns the Gmail messageId and threadId on success. Provide htmlBody for rich formatting; body is used as the plain-text fallback. #### Parameters [Section titled “Parameters”](#parameters) | Name | Type | Required | Default | Description | | ----------------- | ------ | -------- | ------- | ------------------------------------------------------------------------------------------------------- | | `workspaceId` | string | No | — | Workspace ID (defaults to the bound workspace) | | `to` | string | Yes | — | Recipient email address | | `subject` | string | Yes | — | Email subject line (min length: 1) | | `body` | string | Yes | — | Plain-text email body (min length: 1) | | `htmlBody` | string | No | — | Optional HTML email body (used instead of body for rich formatting) | | `googleAccountId` | string | No | — | Google account ID to send from. Defaults to the first connected Google account linked to the workspace. | *** ### set\_google\_app\_password [Section titled “set\_google\_app\_password”](#set_google_app_password) **Full name:** `campaignstack_set_google_app_password` workspace:write Idempotent Set or update the Google App Password for a connected Google account. Validates the App Password via IMAP before storing it encrypted. Required for email warmup (MailReach integration) and future IMAP-based features. Generate an App Password at myaccount.google.com > Security > App Passwords. IMAP must be enabled in Gmail settings. Returns { success: true, status: ‘valid’, authenticatedEmail } on success. Use campaignstack\_list\_integrations to find valid googleAccountId values. #### Parameters [Section titled “Parameters”](#parameters-1) | Name | Type | Required | Default | Description | | ----------------- | ------ | -------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `workspaceId` | string | No | — | Workspace ID (defaults to the bound workspace) | | `googleAccountId` | string | Yes | — | Google account ID to configure the App Password for. Use campaignstack\_list\_integrations to find valid account IDs. (min length: 1) | | `appPassword` | string | Yes | — | 16-character Google App Password (without spaces). Generate one at myaccount.google.com > Security > App Passwords. (min length: 1, max length: 64) | | `email` | string | No | — | Email address to associate with this App Password (defaults to the account’s primary email). Used to verify IMAP authentication matches the correct account. | *** # Email Warmup > Manage automated email warmup for connected Google accounts — enable, disable, check progression, and view workspace-wide warmup states. Manage automated email warmup for connected Google accounts — enable, disable, check progression, and view workspace-wide warmup states. ## Tools (5) [Section titled “Tools (5)”](#tools-5) ### enable\_warmup [Section titled “enable\_warmup”](#enable_warmup) **Full name:** `campaignstack_enable_warmup` workspace:write Enable email warmup for a connected Google account via MailReach. Requires a Google App Password to be configured first (use campaignstack\_set\_google\_app\_password). Costs 6,250 credits (covers the \~4 week warming period). Once the reputation score reaches 80, warmup completes automatically. Optionally specify warmupEmail to warm a verified alias instead of the primary address. Fails if this email address is already being warmed on another account. Returns { ok: true, alreadyEnabled } on success. Use campaignstack\_list\_workspace\_warmups to see all warmup states before enabling. #### Parameters [Section titled “Parameters”](#parameters) | Name | Type | Required | Default | Description | | ----------------- | ------ | -------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------- | | `googleAccountId` | string | Yes | — | Google account ID to enable warmup for. Use campaignstack\_list\_integrations to find valid account IDs. (min length: 1) | | `workspaceId` | string | No | — | Workspace ID (defaults to the bound workspace) | | `warmupEmail` | string | No | — | Email address to warm up (defaults to the account’s primary email). Can be an alias if the alias is verified in the account settings. | *** ### disable\_warmup [Section titled “disable\_warmup”](#disable_warmup) **Full name:** `campaignstack_disable_warmup` workspace:write Destructive Idempotent Disable email warmup for a connected Google account. Credits already used are not refunded. If warmup is restarted later, it begins from week 1 of the ramp-up process (costs credits again). Returns { success: true, previousStatus } where previousStatus is ‘warming’ or ‘warmed’. If no active warmup exists for the account, returns an informational message. Use campaignstack\_list\_workspace\_warmups to see all warmup states. #### Parameters [Section titled “Parameters”](#parameters-1) | Name | Type | Required | Default | Description | | ----------------- | ------ | -------- | ------- | ------------------------------------------------------------------------------------------------------------------------- | | `googleAccountId` | string | Yes | — | Google account ID to disable warmup for. Use campaignstack\_list\_integrations to find valid account IDs. (min length: 1) | | `workspaceId` | string | No | — | Workspace ID (defaults to the bound workspace) | *** ### get\_warmup\_status [Section titled “get\_warmup\_status”](#get_warmup_status) **Full name:** `campaignstack_get_warmup_status` workspace:read Read-only Idempotent Check the email warmup status for a single Google account. Returns provider, status, warmupEmail, score, and daysSinceStart. For MailReach warmup, also returns totalMessagesSent, totalMessagesReceived, and rampupTarget from the last sync. For manual warmup entries, returns a note explaining that no progression data is available. Returns an informational message if no warmup is configured. Use campaignstack\_list\_workspace\_warmups for a workspace-wide overview. Use campaignstack\_enable\_warmup to start automated warmup. #### Parameters [Section titled “Parameters”](#parameters-2) | Name | Type | Required | Default | Description | | ----------------- | ------ | -------- | ------- | ------------------------------------------------------------------------------------------------------------------------------ | | `googleAccountId` | string | Yes | — | Google account ID to check warmup status for. Use campaignstack\_list\_integrations to find valid account IDs. (min length: 1) | | `workspaceId` | string | No | — | Workspace ID (defaults to the bound workspace) | *** ### mark\_as\_warmed\_up [Section titled “mark\_as\_warmed\_up”](#mark_as_warmed_up) **Full name:** `campaignstack_mark_as_warmed_up` workspace:write Idempotent Mark a Google account as already warmed up without using MailReach. Use this when the inbox was warmed externally (another tool, manual warmup, or aged inbox with good reputation). Free — does not cost credits (no MailReach involvement). Fails if this email address already has an active warmup on another account. Returns { ok: true, alreadyEnabled } on success. Use campaignstack\_enable\_warmup instead to start automated warmup via MailReach. #### Parameters [Section titled “Parameters”](#parameters-3) | Name | Type | Required | Default | Description | | ----------------- | ------ | -------- | ------- | ------------------------------------------------------------------------------------------------------------------------ | | `googleAccountId` | string | Yes | — | Google account ID to mark as warmed up. Use campaignstack\_list\_integrations to find valid account IDs. (min length: 1) | | `workspaceId` | string | No | — | Workspace ID (defaults to the bound workspace) | | `warmupEmail` | string | No | — | Email address to mark as warmed (defaults to the account’s primary email). Can be a verified alias. | *** ### list\_workspace\_warmups [Section titled “list\_workspace\_warmups”](#list_workspace_warmups) **Full name:** `campaignstack_list_workspace_warmups` workspace:read Read-only Idempotent List all Google accounts in a workspace with their warmup status. Returns every account (even those without warmup), with status: ‘not\_started’, ‘warming’, ‘warmed’, or ‘disabled’. Includes score, daysSinceStart, provider, warmupEmail, and whether an App Password is configured. Use this to get a workspace-wide warmup overview before enabling or disabling warmup on specific accounts. #### Parameters [Section titled “Parameters”](#parameters-4) | Name | Type | Required | Default | Description | | ------------- | ------ | -------- | ------- | ---------------------------------------------- | | `workspaceId` | string | No | — | Workspace ID (defaults to the bound workspace) | *** # Enrichment Status > Track the progress of lead enrichment batches. Track the progress of lead enrichment batches. ## Tools (4) [Section titled “Tools (4)”](#tools-4) ### get\_enrichment\_status [Section titled “get\_enrichment\_status”](#get_enrichment_status) **Full name:** `campaignstack_get_enrichment_status` leads:read Read-only Idempotent Get the current enrichment queue status for a workspace. Shows counts of pending, processing, completed, and failed items, plus creditsExhausted (pending items deferred because the workspace is out of credits). Optionally filter by campaign. #### Parameters [Section titled “Parameters”](#parameters) | Name | Type | Required | Default | Description | | ------------- | ------ | -------- | ------- | --------------- | | `workspaceId` | string | Yes | — | (min length: 1) | | `campaignId` | string | No | — | | | `platform` | string | No | — | | *** ### queue\_leads [Section titled “queue\_leads”](#queue_leads) **Full name:** `campaignstack_queue_leads` leads:write Idempotent Queue leads for enrichment. Each result is validated, deduped by profile slug, and added to the enrichment pipeline. Invalid profile URLs are skipped with errors reported. Results should come from LinkedIn search page extraction. platform defaults to ‘linkedin’. #### Parameters [Section titled “Parameters”](#parameters-1) | Name | Type | Required | Default | Description | | ------------- | --------- | -------- | ------- | --------------- | | `workspaceId` | string | Yes | — | (min length: 1) | | `campaignId` | string | Yes | — | (min length: 1) | | `accountId` | string | Yes | — | (min length: 1) | | `platform` | string | No | — | | | `results` | object\[] | Yes | — | | *** ### priority\_enrich [Section titled “priority\_enrich”](#priority_enrich) **Full name:** `campaignstack_priority_enrich` leads:write Idempotent Immediately enrich specific LinkedIn profiles, bypassing the normal priority queue. Profiles are queued at highest priority and processing starts immediately. Maximum 50 slugs per request. Use campaignstack\_get\_enrichment\_status to monitor progress. #### Parameters [Section titled “Parameters”](#parameters-2) | Name | Type | Required | Default | Description | | ------------- | --------- | -------- | ------- | --------------- | | `workspaceId` | string | Yes | — | (min length: 1) | | `accountId` | string | Yes | — | (min length: 1) | | `slugs` | string\[] | Yes | — | | | `platform` | string | No | — | | *** ### build\_search\_url [Section titled “build\_search\_url”](#build_search_url) **Full name:** `campaignstack_build_search_url` leads:read Read-only Idempotent Build a LinkedIn People search URL from ICP (Ideal Customer Profile) criteria. Input ICP fields like titles, industries, locations, seniorities, company sizes, and keywords. Returns a ready-to-use LinkedIn search URL. Use campaignstack\_list\_icps to get ICP criteria for a campaign, then pass them here to generate a search URL. The resulting URL can be used with campaignstack\_queue\_leads to import search results. #### Parameters [Section titled “Parameters”](#parameters-3) | Name | Type | Required | Default | Description | | -------------- | --------- | -------- | ------- | ----------- | | `titles` | string\[] | No | — | | | `industries` | string\[] | No | — | | | `locations` | object\[] | No | — | | | `seniorities` | string\[] | No | — | | | `companySizes` | string\[] | No | — | | | `keywords` | string\[] | No | — | | *** # ICPs > Ideal Customer Profile scoring and management. Ideal Customer Profile scoring and management. ## Tools (6) [Section titled “Tools (6)”](#tools-6) ### list\_icps [Section titled “list\_icps”](#list_icps) **Full name:** `campaignstack_list_icps` icps:read Read-only Idempotent List all ICP (Ideal Customer Profile) definitions for a campaign. Returns ICP IDs with criteria summary including target titles, industries, seniorities, and score threshold. Use the returned icpId values with campaignstack\_get\_icp\_scores to view lead match scores, or campaignstack\_trigger\_icp\_scoring to run scoring. #### Parameters [Section titled “Parameters”](#parameters) | Name | Type | Required | Default | Description | | ------------ | ------ | -------- | ------- | --------------- | | `campaignId` | string | Yes | — | (min length: 1) | *** ### get\_icp\_scores [Section titled “get\_icp\_scores”](#get_icp_scores) **Full name:** `campaignstack_get_icp_scores` icps:read Read-only Idempotent Get ICP match scores for leads. Filter by minimum score to find top matches. Returns paginated array of { leadId, leadName, icpMatchScore, matchedFields } sorted by score descending. Use campaignstack\_list\_icps to find ICP IDs. High-scoring leads can be added to lists via campaignstack\_add\_leads\_to\_list. #### Parameters [Section titled “Parameters”](#parameters-1) | Name | Type | Required | Default | Description | | ---------- | ------ | -------- | ------- | ------------------ | | `icpId` | string | Yes | — | (min length: 1) | | `minScore` | number | No | — | (min: 0, max: 100) | | `limit` | number | No | `20` | (min: 1, max: 100) | | `cursor` | string | No | — | | *** ### trigger\_icp\_scoring [Section titled “trigger\_icp\_scoring”](#trigger_icp_scoring) **Full name:** `campaignstack_trigger_icp_scoring` icps:write Idempotent Trigger ICP scoring for specific leads or all leads in the campaign. Scoring runs and persists results — use campaignstack\_get\_icp\_scores to check results afterward. Use campaignstack\_list\_icps to find ICP IDs first. #### Parameters [Section titled “Parameters”](#parameters-2) | Name | Type | Required | Default | Description | | --------- | --------- | -------- | ------- | --------------- | | `icpId` | string | Yes | — | (min length: 1) | | `leadIds` | string\[] | No | — | | *** ### create\_icp [Section titled “create\_icp”](#create_icp) **Full name:** `campaignstack_create_icp` icps:write Create a new ICP (Ideal Customer Profile) for a campaign. Specify target criteria: titles, industries, seniorities, company sizes, and keywords. After creation, use campaignstack\_trigger\_icp\_scoring to score leads against the new ICP. Use campaignstack\_list\_campaigns to find valid campaignId values. #### Parameters [Section titled “Parameters”](#parameters-3) | Name | Type | Required | Default | Description | | -------------- | --------- | -------- | ------- | --------------- | | `campaignId` | string | Yes | — | (min length: 1) | | `titles` | string\[] | No | — | | | `industries` | string\[] | No | — | | | `seniorities` | string\[] | No | — | | | `companySizes` | string\[] | No | — | | | `keywords` | string\[] | No | — | | *** ### update\_icp [Section titled “update\_icp”](#update_icp) **Full name:** `campaignstack_update_icp` icps:write Idempotent Update an existing ICP’s criteria. Only supply the fields you want to change — omitted fields are left unchanged. Updating criteria automatically invalidates cached scores and reschedules scoring variant generation. Use campaignstack\_list\_icps to find ICP IDs. #### Parameters [Section titled “Parameters”](#parameters-4) | Name | Type | Required | Default | Description | | -------------- | --------- | -------- | ------- | --------------- | | `icpId` | string | Yes | — | (min length: 1) | | `titles` | string\[] | No | — | | | `industries` | string\[] | No | — | | | `seniorities` | string\[] | No | — | | | `companySizes` | string\[] | No | — | | | `keywords` | string\[] | No | — | | *** ### delete\_icp [Section titled “delete\_icp”](#delete_icp) **Full name:** `campaignstack_delete_icp` icps:write Destructive Idempotent Permanently delete an ICP and its associated scoring data. This action is irreversible. Use campaignstack\_list\_icps to find ICP IDs before deleting. #### Parameters [Section titled “Parameters”](#parameters-5) | Name | Type | Required | Default | Description | | ------- | ------ | -------- | ------- | --------------- | | `icpId` | string | Yes | — | (min length: 1) | *** # Inbox > Read LinkedIn conversations, check unread counts, mark threads read, refresh the inbox, and send messages in existing conversations. Read LinkedIn conversations, check unread counts, mark threads read, refresh the inbox, and send messages in existing conversations. ## Tools (6) [Section titled “Tools (6)”](#tools-6) ### list\_inbox\_conversations [Section titled “list\_inbox\_conversations”](#list_inbox_conversations) **Full name:** `campaignstack_list_inbox_conversations` inbox:read Read-only Idempotent List inbox conversations across all LinkedIn accounts in the workspace. Returns up to 50 conversations sorted by most recent activity, with participant details, last message preview, read/unread status, and associated lead info. Each conversation includes a conversationRef { platform, id } used as the identifier for other inbox tools. Use unreadOnly: true to filter to unread threads only. Use identityFilter to restrict to specific LinkedIn account IDs (use campaignstack\_list\_accounts to find them). Use campaignstack\_get\_conversation to fetch full message history for a thread. #### Parameters [Section titled “Parameters”](#parameters) | Name | Type | Required | Default | Description | | ---------------- | --------- | -------- | ------- | ----------- | | `workspaceId` | string | No | — | | | `unreadOnly` | boolean | No | — | | | `identityFilter` | string\[] | No | — | | *** ### get\_conversation [Section titled “get\_conversation”](#get_conversation) **Full name:** `campaignstack_get_conversation` inbox:read Read-only Idempotent Get a conversation with its full message history. Takes a conversationRef { platform, id } from campaignstack\_list\_inbox\_conversations. Returns all participants, messages (newest first in the messages array), read state, account info, and associated lead details. If needsFetch is true, messages have not been loaded yet — use campaignstack\_refresh\_inbox to populate them. Use campaignstack\_list\_inbox\_conversations to find conversation refs. #### Parameters [Section titled “Parameters”](#parameters-1) | Name | Type | Required | Default | Description | | ----------------- | ------ | -------- | ------- | ----------- | | `conversationRef` | object | Yes | — | | *** ### get\_inbox\_unread\_count [Section titled “get\_inbox\_unread\_count”](#get_inbox_unread_count) **Full name:** `campaignstack_get_inbox_unread_count` inbox:read Read-only Idempotent Get the total number of unread conversations across all LinkedIn accounts in the workspace. Returns { total, accountCount } — total is the sum of unread conversation counts, accountCount is the number of active LinkedIn accounts in the workspace. Uses denormalized counters for efficiency (no conversation table scan). #### Parameters [Section titled “Parameters”](#parameters-2) | Name | Type | Required | Default | Description | | ------------- | ------ | -------- | ------- | ----------- | | `workspaceId` | string | No | — | | *** ### mark\_conversation\_read [Section titled “mark\_conversation\_read”](#mark_conversation_read) **Full name:** `campaignstack_mark_conversation_read` inbox:write Idempotent Mark a conversation as read. Clears the unread flag and decrements the account’s unread counter. No-ops if the conversation is already read. Use campaignstack\_list\_inbox\_conversations to find conversation refs. #### Parameters [Section titled “Parameters”](#parameters-3) | Name | Type | Required | Default | Description | | ----------------- | ------ | -------- | ------- | ----------- | | `conversationRef` | object | Yes | — | | *** ### refresh\_inbox [Section titled “refresh\_inbox”](#refresh_inbox) **Full name:** `campaignstack_refresh_inbox` inbox:write Trigger an inbox refresh for all LinkedIn accounts in the workspace. Polls LinkedIn for new messages and routes them into CampaignStack conversations. Respects a 20-second freshness dedup window per account (skips recently-fetched accounts). Returns { refreshed, skipped, errors } — refreshed = accounts polled, skipped = fresh/expired/unenriched accounts. After refresh, use campaignstack\_list\_inbox\_conversations to see updated conversations. #### Parameters [Section titled “Parameters”](#parameters-4) | Name | Type | Required | Default | Description | | ------------- | ------ | -------- | ------- | ----------- | | `workspaceId` | string | No | — | | *** ### send\_message [Section titled “send\_message”](#send_message) **Full name:** `campaignstack_send_message` inbox:write Destructive Send a plain-text LinkedIn message in an existing conversation. Takes a conversationRef { platform, id } from campaignstack\_list\_inbox\_conversations. The message is dispatched asynchronously via the runner — the tool returns a messageEntityUrn for tracking. Use campaignstack\_get\_conversation to check delivery status. Subject to daily send\_message budget and business hours gates unless bypassed. When true, bypasses ALL LinkedIn safety limits (daily budget, weekly caps, business hours, account status checks). ⚠️ WARNING: This disables all protections that prevent LinkedIn account restrictions. Use only when you understand the risks and accept that the account may be flagged or restricted by LinkedIn. #### Parameters [Section titled “Parameters”](#parameters-5) | Name | Type | Required | Default | Description | | ------------------------------- | ------- | -------- | ------- | --------------- | | `conversationRef` | object | Yes | — | | | `text` | string | Yes | — | (min length: 1) | | `dangerouslyBypassSafetySystem` | boolean | No | `false` | | *** # Integrations > Connect and manage workspace integrations — Apollo and Instantly — and trigger Apollo list imports. Connect and manage workspace integrations — Apollo and Instantly — and trigger Apollo list imports. ## Tools (7) [Section titled “Tools (7)”](#tools-7) ### list\_integrations [Section titled “list\_integrations”](#list_integrations) **Full name:** `campaignstack_list_integrations` integrations:read Read-only Idempotent List connected integrations for a workspace (Apollo, Instantly, Clay). Returns provider, connection status, and last-tested timestamp. API keys and webhook secrets are never returned — only a boolean indicating they are set. Use campaignstack\_connect\_apollo or campaignstack\_connect\_instantly to add integrations. #### Parameters [Section titled “Parameters”](#parameters) | Name | Type | Required | Default | Description | | ------------- | ------ | -------- | ------- | ---------------------------------------------- | | `workspaceId` | string | No | — | Workspace ID (defaults to the bound workspace) | *** ### list\_apollo\_sources [Section titled “list\_apollo\_sources”](#list_apollo_sources) **Full name:** `campaignstack_list_apollo_sources` integrations:read Read-only List Apollo lists (labels) available in the connected Apollo account. Returns each list’s id, name, and estimated contact count. Use the returned id values with campaignstack\_import\_apollo\_list to import contacts. Requires a connected Apollo integration — use campaignstack\_connect\_apollo first if needed. #### Parameters [Section titled “Parameters”](#parameters-1) | Name | Type | Required | Default | Description | | ------------- | ------ | -------- | ------- | ---------------------------------------------- | | `workspaceId` | string | No | — | Workspace ID (defaults to the bound workspace) | *** ### get\_apollo\_import\_progress [Section titled “get\_apollo\_import\_progress”](#get_apollo_import_progress) **Full name:** `campaignstack_get_apollo_import_progress` integrations:read Read-only Idempotent Get the progress of an in-flight or recently completed Apollo list import. Returns processedCount, importedCount, totalCount, status (in\_progress / completed / failed), and any error message. Poll this after campaignstack\_import\_apollo\_list to track large imports. Use the same listId you passed to import\_apollo\_list. #### Parameters [Section titled “Parameters”](#parameters-2) | Name | Type | Required | Default | Description | | ------------- | ------ | -------- | ------- | ---------------------------------------------- | | `listId` | string | Yes | — | (min length: 1) | | `workspaceId` | string | No | — | Workspace ID (defaults to the bound workspace) | *** ### connect\_apollo [Section titled “connect\_apollo”](#connect_apollo) **Full name:** `campaignstack_connect_apollo` integrations:write Idempotent Connect an Apollo.io account by providing an API key. Validates the key against the Apollo API and stores the integration. Returns success: true when the key is valid and the integration is connected. Returns success: false (with integrationId still created) when the key fails validation. Use campaignstack\_list\_apollo\_sources after connecting to browse available contact lists. #### Parameters [Section titled “Parameters”](#parameters-3) | Name | Type | Required | Default | Description | | ------------- | ------ | -------- | ------- | ---------------------------------------------- | | `apiKey` | string | Yes | — | (min length: 10) | | `workspaceId` | string | No | — | Workspace ID (defaults to the bound workspace) | *** ### connect\_instantly [Section titled “connect\_instantly”](#connect_instantly) **Full name:** `campaignstack_connect_instantly` integrations:write Idempotent Connect an Instantly.ai account by providing an API key. Validates the key against the Instantly API and stores the integration. Returns success: true when the key is valid and the integration is connected. Returns success: false (with integrationId still created) when the key fails validation. #### Parameters [Section titled “Parameters”](#parameters-4) | Name | Type | Required | Default | Description | | ------------- | ------ | -------- | ------- | ---------------------------------------------- | | `apiKey` | string | Yes | — | (min length: 10) | | `workspaceId` | string | No | — | Workspace ID (defaults to the bound workspace) | *** ### disconnect\_integration [Section titled “disconnect\_integration”](#disconnect_integration) **Full name:** `campaignstack_disconnect_integration` integrations:write Destructive Disconnect (remove) an integration by provider. Permanently deletes the integration record and its stored credentials. Valid providers: apollo, instantly, clay. Use campaignstack\_list\_integrations to see which integrations are connected. #### Parameters [Section titled “Parameters”](#parameters-5) | Name | Type | Required | Default | Description | | ------------- | --------------------------------- | -------- | ------- | ---------------------------------------------- | | `provider` | `apollo` \| `instantly` \| `clay` | Yes | — | Integration provider to disconnect | | `workspaceId` | string | No | — | Workspace ID (defaults to the bound workspace) | *** ### import\_apollo\_list [Section titled “import\_apollo\_list”](#import_apollo_list) **Full name:** `campaignstack_import_apollo_list` integrations:write Import leads from an Apollo contact list into CampaignStack. Fetches contacts from the specified Apollo list (up to 50,000 records across 500 pages) and creates or merges leads. Deduplicates by email and LinkedIn URL. Optionally filter by campaign ICP with onlyMatchingIcp: true. For large lists this runs asynchronously — use campaignstack\_get\_apollo\_import\_progress to check status. Use campaignstack\_list\_apollo\_sources to find valid listId values. #### Parameters [Section titled “Parameters”](#parameters-6) | Name | Type | Required | Default | Description | | ----------------- | ------- | -------- | ------- | ------------------------------------------------------------- | | `listId` | string | Yes | — | (min length: 1) | | `listName` | string | No | — | Display name for the Apollo list (for progress tracking) | | `listCount` | integer | No | — | Expected total contact count (for progress display) (min: 0) | | `campaignId` | string | No | — | Campaign ID to filter by ICP (requires onlyMatchingIcp: true) | | `onlyMatchingIcp` | boolean | No | — | Import only contacts matching the campaign ICP | | `workspaceId` | string | No | — | Workspace ID (defaults to the bound workspace) | *** # Lead Lists > Manage collections of leads within campaigns. Manage collections of leads within campaigns. ## Tools (8) [Section titled “Tools (8)”](#tools-8) ### list\_lead\_lists [Section titled “list\_lead\_lists”](#list_lead_lists) **Full name:** `campaignstack_list_lead_lists` lead\_lists:read Read-only Idempotent List all lead lists in a campaign with lead count per list. Returns type (set or query) and source for each list. Use the returned leadListId values with campaignstack\_get\_lead\_list or campaignstack\_add\_leads\_to\_list. Use campaignstack\_list\_campaigns to find valid campaign IDs. #### Parameters [Section titled “Parameters”](#parameters) | Name | Type | Required | Default | Description | | ------------ | ------ | -------- | ------- | --------------- | | `campaignId` | string | Yes | — | (min length: 1) | *** ### get\_lead\_list [Section titled “get\_lead\_list”](#get_lead_list) **Full name:** `campaignstack_get_lead_list` lead\_lists:read Read-only Idempotent Get a lead list by ID with details and optionally paginated leads. Set includeLeads to true to get the leads in the list. If the lead list is not found, use campaignstack\_list\_lead\_lists to find valid IDs. #### Parameters [Section titled “Parameters”](#parameters-1) | Name | Type | Required | Default | Description | | -------------- | ------- | -------- | ------- | ------------------ | | `leadListId` | string | Yes | — | (min length: 1) | | `includeLeads` | boolean | No | `false` | | | `limit` | integer | No | `20` | (min: 1, max: 100) | | `cursor` | string | No | — | | *** ### create\_lead\_list [Section titled “create\_lead\_list”](#create_lead_list) **Full name:** `campaignstack_create_lead_list` lead\_lists:write Create a new static lead list in a campaign. Optionally add leads by providing leadRefs. Each leadRef needs leadSource (default ‘global’) and leadId. Use campaignstack\_list\_campaigns to find valid campaign IDs and campaignstack\_search\_leads to find lead IDs. #### Parameters [Section titled “Parameters”](#parameters-2) | Name | Type | Required | Default | Description | | ------------ | --------- | -------- | ------- | --------------- | | `campaignId` | string | Yes | — | (min length: 1) | | `name` | string | Yes | — | (min length: 1) | | `leadRefs` | object\[] | No | — | | *** ### add\_leads\_to\_list [Section titled “add\_leads\_to\_list”](#add_leads_to_list) **Full name:** `campaignstack_add_leads_to_list` lead\_lists:write Idempotent Add leads to an existing lead list. Idempotent — leads already in the list are skipped. Each leadRef needs leadSource (default ‘global’) and leadId. Returns the count of newly added leads and already-present leads. Use campaignstack\_list\_lead\_lists to find lead list IDs and campaignstack\_search\_leads to find lead IDs. #### Parameters [Section titled “Parameters”](#parameters-3) | Name | Type | Required | Default | Description | | ------------ | --------- | -------- | ------- | --------------- | | `leadListId` | string | Yes | — | (min length: 1) | | `leadRefs` | object\[] | Yes | — | | *** ### update\_lead\_list [Section titled “update\_lead\_list”](#update_lead_list) **Full name:** `campaignstack_update_lead_list` lead\_lists:write Idempotent Update a lead list: rename it and/or update its query predicate. Setting a query converts the list to query type. Use campaignstack\_list\_lead\_lists to find valid lead list IDs. #### Parameters [Section titled “Parameters”](#parameters-4) | Name | Type | Required | Default | Description | | ------------ | ------ | -------- | ------- | --------------- | | `leadListId` | string | Yes | — | (min length: 1) | | `name` | string | No | — | (min length: 1) | | `query` | object | No | — | | *** ### remove\_lead\_list [Section titled “remove\_lead\_list”](#remove_lead_list) **Full name:** `campaignstack_remove_lead_list` lead\_lists:write Destructive Permanently delete a lead list. This is destructive and cannot be undone. Use campaignstack\_list\_lead\_lists to find valid lead list IDs. #### Parameters [Section titled “Parameters”](#parameters-5) | Name | Type | Required | Default | Description | | ------------ | ------ | -------- | ------- | --------------- | | `leadListId` | string | Yes | — | (min length: 1) | *** ### create\_query\_lead\_list [Section titled “create\_query\_lead\_list”](#create_query_lead_list) **Full name:** `campaignstack_create_query_lead_list` lead\_lists:write Create a dynamic query-type lead list in a campaign. Query lists dynamically resolve their members from included/excluded lists and filter rules. Use campaignstack\_list\_campaigns to find campaign IDs and campaignstack\_list\_lead\_lists to find lead list IDs for include/exclude refs. #### Parameters [Section titled “Parameters”](#parameters-6) | Name | Type | Required | Default | Description | | ------------ | ------ | -------- | ------- | --------------- | | `campaignId` | string | Yes | — | (min length: 1) | | `name` | string | Yes | — | (min length: 1) | | `query` | object | Yes | — | | *** ### remove\_leads\_from\_list [Section titled “remove\_leads\_from\_list”](#remove_leads_from_list) **Full name:** `campaignstack_remove_leads_from_list` lead\_lists:write Destructive Idempotent Remove specific leads from a lead list. Each leadRef needs leadSource (default ‘global’) and leadId. Returns the count of leads actually removed. Use campaignstack\_get\_lead\_list with includeLeads=true to see current leads. #### Parameters [Section titled “Parameters”](#parameters-7) | Name | Type | Required | Default | Description | | ------------ | --------- | -------- | ------- | --------------- | | `leadListId` | string | Yes | — | (min length: 1) | | `leadRefs` | object\[] | Yes | — | | *** # Leads > Search, view, and import leads. Search, view, and import leads. ## Tools (7) [Section titled “Tools (7)”](#tools-7) ### search\_leads [Section titled “search\_leads”](#search_leads) **Full name:** `campaignstack_search_leads` leads:read Read-only Idempotent Search leads in a workspace by name, company, or other criteria. Returns paginated results. Platform presence (LinkedIn URL, follower counts, …) is under each lead’s `profiles` key (e.g. profiles.linkedin.url); the top-level linkedInUrl is a legacy alias. Use returned leadId values with campaignstack\_get\_lead or campaignstack\_add\_leads\_to\_list. #### Parameters [Section titled “Parameters”](#parameters) | Name | Type | Required | Default | Description | | ------------- | ------- | -------- | ------- | ----------------------------------- | | `workspaceId` | string | No | — | Defaults to the API key’s workspace | | `query` | string | No | — | | | `companyId` | string | No | — | | | `limit` | integer | No | `20` | (min: 1, max: 100) | | `cursor` | string | No | — | | *** ### get\_lead [Section titled “get\_lead”](#get_lead) **Full name:** `campaignstack_get_lead` leads:read Read-only Idempotent Get a lead by ID with full details including company information and ICP scores. Platform presence is under the `profiles` key (e.g. profiles.linkedin.url, followerCount, bio); top-level fields like linkedInUrl are legacy aliases. If the lead is not found, use campaignstack\_search\_leads to find valid lead IDs. #### Parameters [Section titled “Parameters”](#parameters-1) | Name | Type | Required | Default | Description | | -------- | ------ | -------- | ------- | --------------- | | `leadId` | string | Yes | — | (min length: 1) | *** ### import\_leads\_csv [Section titled “import\_leads\_csv”](#import_leads_csv) **Full name:** `campaignstack_import_leads_csv` leads:write Import leads from CSV data. Provide raw CSV content and optional column mapping. If no mapping is provided, columns are auto-mapped based on header names. After import, use campaignstack\_search\_leads to verify imported leads, or campaignstack\_add\_leads\_to\_list to add them to a campaign. #### Parameters [Section titled “Parameters”](#parameters-2) | Name | Type | Required | Default | Description | | ------------- | ------ | -------- | ------- | ----------------------------------- | | `workspaceId` | string | No | — | Defaults to the API key’s workspace | | `csvData` | string | Yes | — | (min length: 1) | | `mapping` | object | No | — | | *** ### update\_lead\_metadata [Section titled “update\_lead\_metadata”](#update_lead_metadata) **Full name:** `campaignstack_update_lead_metadata` leads:write Idempotent Update user-level metadata (notes and tags) on a lead. Only provided fields are updated. Use campaignstack\_search\_leads or campaignstack\_get\_lead to find lead IDs. #### Parameters [Section titled “Parameters”](#parameters-3) | Name | Type | Required | Default | Description | | ------------- | --------- | -------- | ------- | ----------------------------------- | | `workspaceId` | string | No | — | Defaults to the API key’s workspace | | `leadId` | string | Yes | — | (min length: 1) | | `notes` | string | No | — | | | `tags` | string\[] | No | — | | *** ### update\_lead\_contact\_info [Section titled “update\_lead\_contact\_info”](#update_lead_contact_info) **Full name:** `campaignstack_update_lead_contact_info` leads:write Idempotent Save extracted contact information (email, phone, website) to a lead. Called by the linkedin-contact-info agent after extracting data from LinkedIn’s Contact Info overlay. Only provided fields are updated — omitted fields are left unchanged. #### Parameters [Section titled “Parameters”](#parameters-4) | Name | Type | Required | Default | Description | | ------------- | ------ | -------- | ------- | ----------------------------------- | | `workspaceId` | string | No | — | Defaults to the API key’s workspace | | `leadId` | string | Yes | — | (min length: 1) | | `email` | string | No | — | | | `phone` | string | No | — | | | `website` | string | No | — | | *** ### link\_lead\_profile [Section titled “link\_lead\_profile”](#link_lead_profile) **Full name:** `campaignstack_link_lead_profile` leads:write Idempotent Attach a platform profile (LinkedIn or X) to an existing PRIVATE lead so one person is sequenceable across channels. Provide at least one of url, externalId, or handle. Fails with PROFILE\_ALREADY\_LINKED if the profile is attached to a different lead. API-key callers may only link profiles on private (workspace-scoped) leads — global leads are read-only for user-supplied data. #### Parameters [Section titled “Parameters”](#parameters-5) | Name | Type | Required | Default | Description | | ------------- | ----------------- | -------- | ------- | -------------------------------------------------------------------------------- | | `workspaceId` | string | No | — | Defaults to the API key’s workspace | | `leadId` | string | Yes | — | Private lead id (API-key callers may only link on private leads) (min length: 1) | | `platform` | `linkedin` \| `x` | Yes | — | Platform of the profile being linked | | `url` | string | No | — | Profile URL (e.g. or ) | | `externalId` | string | No | — | Platform-native external id | | `handle` | string | No | — | X handle (with or without @) | *** ### unlink\_lead\_profile [Section titled “unlink\_lead\_profile”](#unlink_lead_profile) **Full name:** `campaignstack_unlink_lead_profile` leads:write Detach a platform profile from a PRIVATE lead into a new standalone lead in the same workspace. Scraped data is never deleted — the profile row is repointed at the new lead. Returns the new lead’s id. API-key callers may only unlink on private leads. #### Parameters [Section titled “Parameters”](#parameters-6) | Name | Type | Required | Default | Description | | ------------- | ----------------- | -------- | ------- | ------------------------------------------------------------- | | `workspaceId` | string | No | — | Defaults to the API key’s workspace | | `leadId` | string | Yes | — | Private lead id currently holding the profile (min length: 1) | | `platform` | `linkedin` \| `x` | Yes | — | Platform of the profile being detached | *** # LinkedIn Accounts > Monitor LinkedIn account health, status, budget usage, and ramp-up state. Monitor LinkedIn account health, status, budget usage, and ramp-up state. ## Tools (6) [Section titled “Tools (6)”](#tools-6) ### list\_accounts [Section titled “list\_accounts”](#list_accounts) **Full name:** `campaignstack_list_accounts` accounts:read Read-only Idempotent List accounts connected to a workspace. Returns LinkedIn and Google accounts with a platform field. Optionally filter by platform (‘linkedin’ or ‘google’). LinkedIn accounts include today’s budget usage; Google accounts include email and status. #### Parameters [Section titled “Parameters”](#parameters) | Name | Type | Required | Default | Description | | ------------- | ---------------------- | -------- | ------- | ------------------------------------------------ | | `workspaceId` | string | Yes | — | (min length: 1) | | `platform` | `linkedin` \| `google` | No | — | Filter by platform. Omit to return all accounts. | *** ### get\_account [Section titled “get\_account”](#get_account) **Full name:** `campaignstack_get_account` accounts:read Read-only Idempotent Get detailed account health info including status, budget usage per action type, ramp-up state, and last activity timestamps. Requires platform (‘linkedin’ or ‘google’) and accountId. Use campaignstack\_list\_accounts to find valid accountId values. #### Parameters [Section titled “Parameters”](#parameters-1) | Name | Type | Required | Default | Description | | ----------- | ---------------------- | -------- | ------- | ----------------------------------- | | `platform` | `linkedin` \| `google` | Yes | — | Platform of the account to retrieve | | `accountId` | string | Yes | — | (min length: 1) | *** ### start\_connections\_export [Section titled “start\_connections\_export”](#start_connections_export) **Full name:** `campaignstack_start_connections_export` accounts:write Start an async runner job that exports the 1st-degree LinkedIn connections for the given account as leads. The job runs in the background — use campaignstack\_get\_automation\_jobs to monitor progress. Imported connections are added to the shared leads database and can be added to campaigns via lead lists. Use campaignstack\_list\_accounts to find valid accountId values (platform: ‘linkedin’). #### Parameters [Section titled “Parameters”](#parameters-2) | Name | Type | Required | Default | Description | | ---------------- | ------- | -------- | ------- | ------------------------------------------------------------------- | | `accountId` | string | Yes | — | (min length: 1) | | `workspaceId` | string | Yes | — | (min length: 1) | | `maxConnections` | integer | No | — | Maximum number of connections to export (default 2500) (max: 10000) | *** ### get\_private\_proxy [Section titled “get\_private\_proxy”](#get_private_proxy) **Full name:** `campaignstack_get_private_proxy` accounts:read Read-only Idempotent Get the private proxy connection info (host, port, username — NEVER the password) for a LinkedIn account, or null if the account is on a shared pool proxy. You can only read proxies for accounts in your own workspace. #### Parameters [Section titled “Parameters”](#parameters-3) | Name | Type | Required | Default | Description | | ------------- | ------ | -------- | ------- | --------------- | | `accountId` | string | Yes | — | (min length: 1) | | `workspaceId` | string | Yes | — | (min length: 1) | *** ### set\_private\_proxy [Section titled “set\_private\_proxy”](#set_private_proxy) **Full name:** `campaignstack_set_private_proxy` accounts:write Idempotent Set (or replace) a private ‘bring-your-own’ HTTP proxy on a LinkedIn account. The proxy is TESTED first; if it cannot reach LinkedIn, NOTHING changes (the current proxy keeps serving runs) and an error is returned. On success the account uses your proxy and any shared pool proxy is released. Use campaignstack\_list\_accounts to find valid accountId values (platform: ‘linkedin’). #### Parameters [Section titled “Parameters”](#parameters-4) | Name | Type | Required | Default | Description | | ------------- | ------- | -------- | ------- | -------------------- | | `accountId` | string | Yes | — | (min length: 1) | | `workspaceId` | string | Yes | — | (min length: 1) | | `host` | string | Yes | — | (min length: 1) | | `port` | integer | Yes | — | (min: 1, max: 65535) | | `username` | string | No | — | | | `password` | string | No | — | | *** ### remove\_private\_proxy [Section titled “remove\_private\_proxy”](#remove_private_proxy) **Full name:** `campaignstack_remove_private_proxy` accounts:write Destructive Idempotent Remove the private proxy from a LinkedIn account and revert it to a shared pool proxy. No-op if the account is already on a shared proxy. #### Parameters [Section titled “Parameters”](#parameters-5) | Name | Type | Required | Default | Description | | ------------- | ------ | -------- | ------- | --------------- | | `accountId` | string | Yes | — | (min length: 1) | | `workspaceId` | string | Yes | — | (min length: 1) | *** # Metrics & Billing > Read-only performance and account telemetry — workspace/campaign metric trends, credit balance, and billing/subscription status. Read-only performance and account telemetry — workspace/campaign metric trends, credit balance, and billing/subscription status. ## Tools (4) [Section titled “Tools (4)”](#tools-4) ### get\_workspace\_metrics [Section titled “get\_workspace\_metrics”](#get_workspace_metrics) **Full name:** `campaignstack_get_workspace_metrics` workspace:read Read-only Idempotent Get workspace-level metrics: latest snapshot plus a time-series for the requested date range. Includes total campaigns, active campaigns, leads added/contacted/converted today, and the top campaign. Use campaignstack\_get\_campaign\_metrics for per-campaign detail. #### Parameters [Section titled “Parameters”](#parameters) | Name | Type | Required | Default | Description | | ------------- | ------- | -------- | ------- | -------------------------------------------------------------------------------------- | | `workspaceId` | string | No | — | Defaults to the API key’s workspace | | `days` | integer | No | `30` | Number of calendar days to include in the range (default 30, max 90) (min: 1, max: 90) | *** ### get\_campaign\_metrics [Section titled “get\_campaign\_metrics”](#get_campaign_metrics) **Full name:** `campaignstack_get_campaign_metrics` campaigns:read Read-only Idempotent Get per-campaign metrics: latest snapshot plus a time-series for the requested date range. Includes leads contacted, responded, converted, failed, total leads, and average ICP match score. Use campaignstack\_list\_campaigns to find valid campaign IDs. The campaign must belong to your API key’s workspace; metrics for campaigns in other workspaces are not returned. Use campaignstack\_get\_workspace\_metrics for a workspace-level summary. #### Parameters [Section titled “Parameters”](#parameters-1) | Name | Type | Required | Default | Description | | ------------ | ------- | -------- | ------- | -------------------------------------------------------------------------------------- | | `campaignId` | string | Yes | — | (min length: 1) | | `days` | integer | No | `30` | Number of calendar days to include in the range (default 30, max 90) (min: 1, max: 90) | *** ### get\_credit\_balance [Section titled “get\_credit\_balance”](#get_credit_balance) **Full name:** `campaignstack_get_credit_balance` workspace:read Read-only Idempotent Get the current AI credit balance for a workspace. Returns monthly usage, included credits, rolled-over credits, pack balance, overage used, total pool size, remaining credits, percent used, and plan type. #### Parameters [Section titled “Parameters”](#parameters-2) | Name | Type | Required | Default | Description | | ------------- | ------ | -------- | ------- | ----------------------------------- | | `workspaceId` | string | No | — | Defaults to the API key’s workspace | *** ### get\_workspace\_billing [Section titled “get\_workspace\_billing”](#get_workspace_billing) **Full name:** `campaignstack_get_workspace_billing` workspace:read Read-only Idempotent Get billing information for a workspace: plan (free/paid), billing cycle, workspace status, and Stripe subscription details (period start/end). Use campaignstack\_get\_credit\_balance to see credit usage. #### Parameters [Section titled “Parameters”](#parameters-3) | Name | Type | Required | Default | Description | | ------------- | ------ | -------- | ------- | ----------------------------------- | | `workspaceId` | string | No | — | Defaults to the API key’s workspace | *** # Notifications > Read and acknowledge workspace notifications — list, count unread, and mark read. Read and acknowledge workspace notifications — list, count unread, and mark read. ## Tools (4) [Section titled “Tools (4)”](#tools-4) ### list\_notifications [Section titled “list\_notifications”](#list_notifications) **Full name:** `campaignstack_list_notifications` notifications:read Read-only Idempotent List notifications for a workspace. Returns paginated notifications most-recent first. Filter by type to see only specific notifications (e.g. pending\_review, credits\_exhausted, account\_risk, reply\_received). Use campaignstack\_count\_unread\_notifications to get the unread badge count. Use campaignstack\_mark\_notification\_read or campaignstack\_mark\_all\_notifications\_read to mark as read. #### Parameters [Section titled “Parameters”](#parameters) | Name | Type | Required | Default | Description | | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ------- | ------------------ | | `workspaceId` | string | No | — | | | `type` | `pending_review` \| `credits_exhausted` \| `credits_low` \| `trial_ending` \| `account_risk` \| `proxy_unreachable` \| `connection_accepted` \| `reply_received` \| `workflow_notify` \| `weekly_report` | No | — | | | `limit` | number | No | `20` | (min: 1, max: 100) | | `cursor` | string | No | — | | *** ### count\_unread\_notifications [Section titled “count\_unread\_notifications”](#count_unread_notifications) **Full name:** `campaignstack_count_unread_notifications` notifications:read Read-only Idempotent Count unread notifications for a workspace. Returns the total number of unread notifications — useful for badge counts. Use campaignstack\_list\_notifications to see the actual notifications. #### Parameters [Section titled “Parameters”](#parameters-1) | Name | Type | Required | Default | Description | | ------------- | ------ | -------- | ------- | ----------- | | `workspaceId` | string | No | — | | *** ### mark\_notification\_read [Section titled “mark\_notification\_read”](#mark_notification_read) **Full name:** `campaignstack_mark_notification_read` notifications:write Idempotent Mark a single notification as read. Use campaignstack\_list\_notifications to find notificationId values. To mark all notifications read at once, use campaignstack\_mark\_all\_notifications\_read. #### Parameters [Section titled “Parameters”](#parameters-2) | Name | Type | Required | Default | Description | | ---------------- | ------ | -------- | ------- | --------------- | | `notificationId` | string | Yes | — | (min length: 1) | *** ### mark\_all\_notifications\_read [Section titled “mark\_all\_notifications\_read”](#mark_all_notifications_read) **Full name:** `campaignstack_mark_all_notifications_read` notifications:write Idempotent Mark all unread notifications in a workspace as read. Large backlogs are cleared asynchronously; the unread badge drops immediately. To mark a single notification read, use campaignstack\_mark\_notification\_read instead. #### Parameters [Section titled “Parameters”](#parameters-3) | Name | Type | Required | Default | Description | | ------------- | ------ | -------- | ------- | ----------- | | `workspaceId` | string | No | — | | *** # Outreach > Send LinkedIn connection requests, likes, comments, follows, and InMail — gated by the safety/budget system with optional bypass. Send LinkedIn connection requests, likes, comments, follows, and InMail — gated by the safety/budget system with optional bypass. ## Tools (5) [Section titled “Tools (5)”](#tools-5) ### send\_connection\_request [Section titled “send\_connection\_request”](#send_connection_request) **Full name:** `campaignstack_send_connection_request` outreach:write Destructive Send a LinkedIn connection request to a profile. Target can be specified as a profileUrl (LinkedIn profile URL) or a leadId (resolved server-side to the lead’s LinkedIn URL). Optionally include a personalized note (max 300 chars, LinkedIn limit). If only one LinkedIn account is connected to the workspace it is used automatically; if multiple exist, specify accountId (use campaignstack\_list\_accounts to find it). Subject to daily connection\_request budget, weekly cap, and business hours gates unless bypassed. When true, bypasses ALL LinkedIn safety limits (daily budget, weekly caps, business hours, account status checks). ⚠️ WARNING: This disables all protections that prevent LinkedIn account restrictions. Use only when you understand the risks and accept that the account may be flagged or restricted by LinkedIn. *** ### like\_post [Section titled “like\_post”](#like_post) **Full name:** `campaignstack_like_post` outreach:write Like a lead’s most recent LinkedIn post to warm the relationship through engagement. Target can be specified as a profileUrl (LinkedIn profile URL) or a leadId (resolved server-side). If only one LinkedIn account is connected to the workspace it is used automatically; if multiple exist, specify accountId (use campaignstack\_list\_accounts to find it). Subject to daily post\_like budget and business hours gates unless bypassed. When true, bypasses ALL LinkedIn safety limits (daily budget, weekly caps, business hours, account status checks). ⚠️ WARNING: This disables all protections that prevent LinkedIn account restrictions. Use only when you understand the risks and accept that the account may be flagged or restricted by LinkedIn. *** ### comment\_on\_post [Section titled “comment\_on\_post”](#comment_on_post) **Full name:** `campaignstack_comment_on_post` outreach:write Comment on a lead’s most recent LinkedIn post with provided text. Target can be specified as a profileUrl (LinkedIn profile URL) or a leadId (resolved server-side). commentText is the pre-written comment (use campaignstack\_craft\_comment to generate one). If only one LinkedIn account is connected to the workspace it is used automatically; if multiple exist, specify accountId (use campaignstack\_list\_accounts to find it). Subject to daily post\_comment budget and business hours gates unless bypassed. When true, bypasses ALL LinkedIn safety limits (daily budget, weekly caps, business hours, account status checks). ⚠️ WARNING: This disables all protections that prevent LinkedIn account restrictions. Use only when you understand the risks and accept that the account may be flagged or restricted by LinkedIn. *** ### follow\_profile [Section titled “follow\_profile”](#follow_profile) **Full name:** `campaignstack_follow_profile` outreach:write Idempotent Follow a LinkedIn profile to stay updated on their posts and activity. Target can be specified as a profileUrl (LinkedIn profile URL) or a leadId (resolved server-side). If only one LinkedIn account is connected to the workspace it is used automatically; if multiple exist, specify accountId (use campaignstack\_list\_accounts to find it). Subject to daily profile\_follow budget and business hours gates unless bypassed. When true, bypasses ALL LinkedIn safety limits (daily budget, weekly caps, business hours, account status checks). ⚠️ WARNING: This disables all protections that prevent LinkedIn account restrictions. Use only when you understand the risks and accept that the account may be flagged or restricted by LinkedIn. *** ### send\_inmail [Section titled “send\_inmail”](#send_inmail) **Full name:** `campaignstack_send_inmail` outreach:write Destructive Send a LinkedIn InMail to a non-connected profile. Requires a Premium, Sales Navigator, or Recruiter subscription on the sending account. Target can be specified as a profileUrl (LinkedIn profile URL) or a leadId (resolved server-side). Requires subject (max 200 chars) and messageText (max 1900 chars). If only one LinkedIn account is connected to the workspace it is used automatically; if multiple exist, specify accountId (use campaignstack\_list\_accounts to find it). Subject to daily send\_inmail budget and business hours gates unless bypassed. Free-tier LinkedIn accounts have a budget of 0 (InMail requires a paid subscription). When true, bypasses ALL LinkedIn safety limits (daily budget, weekly caps, business hours, account status checks). ⚠️ WARNING: This disables all protections that prevent LinkedIn account restrictions. Use only when you understand the risks and accept that the account may be flagged or restricted by LinkedIn. *** # Personas & Phases > Manage a campaign's buyer personas and outreach phases — list, get, create, update, and delete. Manage a campaign’s buyer personas and outreach phases — list, get, create, update, and delete. ## Tools (10) [Section titled “Tools (10)”](#tools-10) ### list\_personas [Section titled “list\_personas”](#list_personas) **Full name:** `campaignstack_list_personas` campaigns:read Read-only Idempotent List all personas for a campaign. Personas define buyer archetypes with pain points, goals, and objections. Use campaignstack\_list\_campaigns to find valid campaign IDs. #### Parameters [Section titled “Parameters”](#parameters) | Name | Type | Required | Default | Description | | ------------ | ------ | -------- | ------- | --------------- | | `campaignId` | string | Yes | — | (min length: 1) | *** ### get\_persona [Section titled “get\_persona”](#get_persona) **Full name:** `campaignstack_get_persona` campaigns:read Read-only Idempotent Get a persona by ID. Returns the full persona record including pain points, goals, and objections. If not found, use campaignstack\_list\_personas with a valid campaignId. #### Parameters [Section titled “Parameters”](#parameters-1) | Name | Type | Required | Default | Description | | ----------- | ------ | -------- | ------- | --------------- | | `personaId` | string | Yes | — | (min length: 1) | *** ### create\_persona [Section titled “create\_persona”](#create_persona) **Full name:** `campaignstack_create_persona` campaigns:write Create a new persona in a campaign. Personas represent buyer archetypes with their pain points, goals, and objections. Use campaignstack\_list\_campaigns to find campaign IDs. #### Parameters [Section titled “Parameters”](#parameters-2) | Name | Type | Required | Default | Description | | ------------- | --------- | -------- | ------- | --------------------------------------- | | `campaignId` | string | Yes | — | (min length: 1) | | `name` | string | Yes | — | (min length: 1) | | `description` | string | No | — | | | `painPoints` | string\[] | No | — | Pain points this persona experiences | | `goals` | string\[] | No | — | Goals this persona is trying to achieve | | `objections` | string\[] | No | — | Common objections from this persona | *** ### update\_persona [Section titled “update\_persona”](#update_persona) **Full name:** `campaignstack_update_persona` campaigns:write Idempotent Update a persona’s name, description, pain points, goals, or objections. Only provided fields are updated. Returns the updated persona object. #### Parameters [Section titled “Parameters”](#parameters-3) | Name | Type | Required | Default | Description | | ------------- | --------- | -------- | ------- | --------------- | | `personaId` | string | Yes | — | (min length: 1) | | `name` | string | No | — | (min length: 1) | | `description` | string | No | — | | | `painPoints` | string\[] | No | — | | | `goals` | string\[] | No | — | | | `objections` | string\[] | No | — | | *** ### delete\_persona [Section titled “delete\_persona”](#delete_persona) **Full name:** `campaignstack_delete_persona` campaigns:write Destructive Permanently delete a persona. This action cannot be undone. Use campaignstack\_list\_personas to confirm the persona ID before deleting. #### Parameters [Section titled “Parameters”](#parameters-4) | Name | Type | Required | Default | Description | | ----------- | ------ | -------- | ------- | --------------- | | `personaId` | string | Yes | — | (min length: 1) | *** ### list\_phases [Section titled “list\_phases”](#list_phases) **Full name:** `campaignstack_list_phases` campaigns:read Read-only Idempotent List all phases for a campaign, sorted by order. Phases represent roadmap milestones with objectives and a phase type. Use campaignstack\_list\_campaigns to find valid campaign IDs. #### Parameters [Section titled “Parameters”](#parameters-5) | Name | Type | Required | Default | Description | | ------------ | ------ | -------- | ------- | --------------- | | `campaignId` | string | Yes | — | (min length: 1) | *** ### get\_phase [Section titled “get\_phase”](#get_phase) **Full name:** `campaignstack_get_phase` campaigns:read Read-only Idempotent Get a campaign phase by ID. Returns the full phase record including objectives, phase type, and order. If not found, use campaignstack\_list\_phases with a valid campaignId. #### Parameters [Section titled “Parameters”](#parameters-6) | Name | Type | Required | Default | Description | | --------- | ------ | -------- | ------- | --------------- | | `phaseId` | string | Yes | — | (min length: 1) | *** ### create\_phase [Section titled “create\_phase”](#create_phase) **Full name:** `campaignstack_create_phase` campaigns:write Create a new campaign phase (roadmap milestone). Phases organize a campaign into sequential steps with objectives. The order field controls phase sequencing. Use campaignstack\_list\_campaigns to find campaign IDs. #### Parameters [Section titled “Parameters”](#parameters-7) | Name | Type | Required | Default | Description | | ------------- | ----------------------------------------------------------------------- | -------- | ------- | ----------------------------- | | `campaignId` | string | Yes | — | (min length: 1) | | `name` | string | Yes | — | (min length: 1) | | `description` | string | Yes | — | (min length: 1) | | `objectives` | string\[] | Yes | — | | | `phaseType` | `sourcing` \| `outreach` \| `content` \| `intelligence` \| `refinement` | No | — | Optional phase classification | | `order` | integer | Yes | — | (min: 0) | *** ### update\_phase [Section titled “update\_phase”](#update_phase) **Full name:** `campaignstack_update_phase` campaigns:write Idempotent Update a campaign phase’s name, description, objectives, phase type, or order. Only provided fields are updated. Returns the updated phase object. #### Parameters [Section titled “Parameters”](#parameters-8) | Name | Type | Required | Default | Description | | ------------- | ----------------------------------------------------------------------- | -------- | ------- | --------------- | | `phaseId` | string | Yes | — | (min length: 1) | | `name` | string | No | — | (min length: 1) | | `description` | string | No | — | | | `objectives` | string\[] | No | — | | | `phaseType` | `sourcing` \| `outreach` \| `content` \| `intelligence` \| `refinement` | No | — | | | `order` | integer | No | — | (min: 0) | *** ### delete\_phase [Section titled “delete\_phase”](#delete_phase) **Full name:** `campaignstack_delete_phase` campaigns:write Destructive Permanently delete a campaign phase. This action cannot be undone. Use campaignstack\_list\_phases to confirm the phase ID before deleting. #### Parameters [Section titled “Parameters”](#parameters-9) | Name | Type | Required | Default | Description | | --------- | ------ | -------- | ------- | --------------- | | `phaseId` | string | Yes | — | (min length: 1) | *** # Signal Agents > Create and manage signal agents (audiences) — the engagement-intelligence feature that watches posts/events and turns engagers into scored leads. Create and manage signal agents (audiences) — the engagement-intelligence feature that watches posts/events and turns engagers into scored leads. ## Tools (9) [Section titled “Tools (9)”](#tools-9) ### list\_signal\_agents [Section titled “list\_signal\_agents”](#list_signal_agents) **Full name:** `campaignstack_list_signal_agents` signals:read Read-only Idempotent List all signal agents (audience intelligence watchers) for a workspace. Signal agents monitor LinkedIn profiles or company feeds and detect engagements. Returns agents with status, type, target info, and counters. Use campaignstack\_get\_signal\_agent for full details, or campaignstack\_get\_signal\_agent\_stats for dashboard metrics. #### Parameters [Section titled “Parameters”](#parameters) | Name | Type | Required | Default | Description | | ----------------- | ------- | -------- | ------- | --------------- | | `workspaceId` | string | No | — | (min length: 1) | | `includeArchived` | boolean | No | — | | *** ### get\_signal\_agent [Section titled “get\_signal\_agent”](#get_signal_agent) **Full name:** `campaignstack_get_signal_agent` signals:read Read-only Idempotent Get a single signal agent with full details including status, target info, account assignments, response mode, and counters (posts watched, signals total). Use campaignstack\_list\_signal\_agents to find agent IDs. #### Parameters [Section titled “Parameters”](#parameters-1) | Name | Type | Required | Default | Description | | --------- | ------ | -------- | ------- | --------------- | | `agentId` | string | Yes | — | (min length: 1) | *** ### get\_signal\_agent\_stats [Section titled “get\_signal\_agent\_stats”](#get_signal_agent_stats) **Full name:** `campaignstack_get_signal_agent_stats` signals:read Read-only Idempotent Get dashboard metrics for a signal agent: total signals, signals in the last 24h, leads created, posts watched, and breakdown by priority (P0/P1/P2). Use campaignstack\_list\_signal\_agents to find agent IDs. #### Parameters [Section titled “Parameters”](#parameters-2) | Name | Type | Required | Default | Description | | --------- | ------ | -------- | ------- | --------------- | | `agentId` | string | Yes | — | (min length: 1) | *** ### list\_signal\_agent\_leads [Section titled “list\_signal\_agent\_leads”](#list_signal_agent_leads) **Full name:** `campaignstack_list_signal_agent_leads` signals:read Read-only Idempotent List leads discovered by a signal agent from its associated lead list. Returns lead records with entry metadata (status, createdAt) sorted newest first. Use campaignstack\_list\_signal\_agents to find agent IDs. #### Parameters [Section titled “Parameters”](#parameters-3) | Name | Type | Required | Default | Description | | --------- | ------ | -------- | ------- | ------------------ | | `agentId` | string | Yes | — | (min length: 1) | | `limit` | number | No | `50` | (min: 1, max: 200) | *** ### create\_signal\_agent [Section titled “create\_signal\_agent”](#create_signal_agent) **Full name:** `campaignstack_create_signal_agent` signals:write Create a new signal agent that monitors a LinkedIn profile, company feed, group, or post for engagements. Auto-generates a template workflow and lead list. target specifies what to watch: { platform: ‘linkedin’, kind: ‘profile’, urn: ’…’ } for profile/company feeds, { platform: ‘linkedin’, kind: ‘group’, url: ’…’, groupId: ’…’ } for groups, or { platform: ‘linkedin’, kind: ‘post’, url: ’…’, activityId: ’…’ } for specific posts. accountIds are the accounts that observe the feed; actAccountIds are the accounts that act on signals. Use campaignstack\_list\_accounts to find account IDs. #### Parameters [Section titled “Parameters”](#parameters-4) | Name | Type | Required | Default | Description | | ------------------- | ---------------------------------------------------------------------- | -------- | ------- | --------------- | | `workspaceId` | string | No | — | (min length: 1) | | `name` | string | Yes | — | (min length: 1) | | `type` | `profile_feed` \| `company_feed` \| `group_watch` \| `post_engagement` | Yes | — | | | `target` | object \| object \| object | Yes | — | | | `targetName` | string | Yes | — | (min length: 1) | | `responseMode` | `auto_send` \| `review_first` | Yes | — | | | `accountIds` | string\[] | Yes | — | | | `actAccountIds` | string\[] | Yes | — | | | `publicReplyConfig` | object | No | — | | *** ### update\_signal\_agent [Section titled “update\_signal\_agent”](#update_signal_agent) **Full name:** `campaignstack_update_signal_agent` signals:write Idempotent Update a signal agent’s configuration. Only provided fields are updated. Can change name, response mode (auto\_send / review\_first), observing account list (accountIds), acting account list (actAccountIds), or public reply configuration. Use campaignstack\_list\_signal\_agents to find agent IDs. #### Parameters [Section titled “Parameters”](#parameters-5) | Name | Type | Required | Default | Description | | ------------------- | ----------------------------- | -------- | ------- | --------------- | | `agentId` | string | Yes | — | (min length: 1) | | `name` | string | No | — | (min length: 1) | | `responseMode` | `auto_send` \| `review_first` | No | — | | | `accountIds` | string\[] | No | — | | | `actAccountIds` | string\[] | No | — | | | `publicReplyConfig` | object | No | — | | *** ### pause\_signal\_agent [Section titled “pause\_signal\_agent”](#pause_signal_agent) **Full name:** `campaignstack_pause_signal_agent` signals:write Idempotent Pause a signal agent, stopping feed polling and workflow execution. The agent and its data are preserved — use campaignstack\_resume\_signal\_agent to resume. Use campaignstack\_list\_signal\_agents to find agent IDs. #### Parameters [Section titled “Parameters”](#parameters-6) | Name | Type | Required | Default | Description | | --------- | ------ | -------- | ------- | --------------- | | `agentId` | string | Yes | — | (min length: 1) | *** ### resume\_signal\_agent [Section titled “resume\_signal\_agent”](#resume_signal_agent) **Full name:** `campaignstack_resume_signal_agent` signals:write Idempotent Resume a paused signal agent, re-enabling feed polling and workflow execution. Use campaignstack\_list\_signal\_agents to find agent IDs. #### Parameters [Section titled “Parameters”](#parameters-7) | Name | Type | Required | Default | Description | | --------- | ------ | -------- | ------- | --------------- | | `agentId` | string | Yes | — | (min length: 1) | *** ### delete\_signal\_agent [Section titled “delete\_signal\_agent”](#delete_signal_agent) **Full name:** `campaignstack_delete_signal_agent` signals:write Destructive Idempotent Archive (soft-delete) a signal agent. Stops polling, pauses the linked workflow, disables all watched posts, and clears competitor watch linkage. Data (signals, leads, posts) is preserved for audit. Use campaignstack\_list\_signal\_agents to find agent IDs. #### Parameters [Section titled “Parameters”](#parameters-8) | Name | Type | Required | Default | Description | | --------- | ------ | -------- | ------- | --------------- | | `agentId` | string | Yes | — | (min length: 1) | *** # Signal Exclusions > Manage which leads and LinkedIn URLs a signal agent excludes from processing — view config, list/add/remove manual exclusion URLs, and toggle exclusions. Manage which leads and LinkedIn URLs a signal agent excludes from processing — view config, list/add/remove manual exclusion URLs, and toggle exclusions. ## Tools (5) [Section titled “Tools (5)”](#tools-5) ### get\_signal\_exclusions [Section titled “get\_signal\_exclusions”](#get_signal_exclusions) **Full name:** `campaignstack_get_signal_exclusions` signals:read Read-only Idempotent Get the signal exclusion config for a workspace. Returns all exclusion entries (manual, csv, lead\_list, filter) with their enabled status and config. Use exclusionId values with campaignstack\_list\_manual\_exclusion\_urls, campaignstack\_add\_manual\_exclusion\_urls, campaignstack\_remove\_manual\_exclusion\_url, or campaignstack\_toggle\_exclusion. Use campaignstack\_list\_signal\_agents to find signal agents for context. #### Parameters [Section titled “Parameters”](#parameters) | Name | Type | Required | Default | Description | | ------------- | ------ | -------- | ------- | ----------------------------------- | | `workspaceId` | string | No | — | Defaults to the API key’s workspace | *** ### list\_manual\_exclusion\_urls [Section titled “list\_manual\_exclusion\_urls”](#list_manual_exclusion_urls) **Full name:** `campaignstack_list_manual_exclusion_urls` signals:read Read-only Idempotent List the LinkedIn profile URLs stored in a manual exclusion entry. Use campaignstack\_get\_signal\_exclusions to find exclusionId values. Returns importedLeadId values usable with campaignstack\_remove\_manual\_exclusion\_url. #### Parameters [Section titled “Parameters”](#parameters-1) | Name | Type | Required | Default | Description | | ------------- | ------ | -------- | ------- | ----------------------------------- | | `workspaceId` | string | No | — | Defaults to the API key’s workspace | | `exclusionId` | string | Yes | — | (min length: 1) | *** ### add\_manual\_exclusion\_urls [Section titled “add\_manual\_exclusion\_urls”](#add_manual_exclusion_urls) **Full name:** `campaignstack_add_manual_exclusion_urls` signals:write Add LinkedIn profile URLs to a manual exclusion entry. Each URL is validated and normalized. Duplicate URLs within the same exclusion are skipped and reported. Returns per-URL results indicating success or error. Use campaignstack\_get\_signal\_exclusions to find exclusionId values for manual exclusion entries. Use campaignstack\_list\_signal\_agents to find signal agents. #### Parameters [Section titled “Parameters”](#parameters-2) | Name | Type | Required | Default | Description | | ------------- | --------- | -------- | ------- | -------------------------------------------------- | | `workspaceId` | string | No | — | Defaults to the API key’s workspace | | `exclusionId` | string | Yes | — | (min length: 1) | | `profileUrls` | string\[] | Yes | — | LinkedIn profile URLs to add to the exclusion list | *** ### remove\_manual\_exclusion\_url [Section titled “remove\_manual\_exclusion\_url”](#remove_manual_exclusion_url) **Full name:** `campaignstack_remove_manual_exclusion_url` signals:write Destructive Remove a LinkedIn profile URL from a manual exclusion entry by its importedLeadId. Use campaignstack\_list\_manual\_exclusion\_urls to find importedLeadId values. Use campaignstack\_get\_signal\_exclusions to find exclusionId values. #### Parameters [Section titled “Parameters”](#parameters-3) | Name | Type | Required | Default | Description | | ---------------- | ------ | -------- | ------- | ----------------------------------- | | `workspaceId` | string | No | — | Defaults to the API key’s workspace | | `exclusionId` | string | Yes | — | (min length: 1) | | `importedLeadId` | string | Yes | — | (min length: 1) | *** ### toggle\_exclusion [Section titled “toggle\_exclusion”](#toggle_exclusion) **Full name:** `campaignstack_toggle_exclusion` signals:write Idempotent Enable or disable a signal exclusion entry. Disabled exclusions are not evaluated during signal agent processing. Use campaignstack\_get\_signal\_exclusions to find exclusionId values. Use campaignstack\_list\_signal\_agents to find signal agents for context. #### Parameters [Section titled “Parameters”](#parameters-4) | Name | Type | Required | Default | Description | | ------------- | ------- | -------- | ------- | -------------------------------------- | | `workspaceId` | string | No | — | Defaults to the API key’s workspace | | `exclusionId` | string | Yes | — | (min length: 1) | | `enabled` | boolean | Yes | — | Whether the exclusion should be active | *** # Signals & Scoring > Understand lead scoring — view score breakdowns, signal history, and campaign weight configuration. Understand lead scoring — view score breakdowns, signal history, and campaign weight configuration. ## Tools (4) [Section titled “Tools (4)”](#tools-4) ### get\_lead\_score\_breakdown [Section titled “get\_lead\_score\_breakdown”](#get_lead_score_breakdown) **Full name:** `campaignstack_get_lead_score_breakdown` leads:read Read-only Idempotent Get a detailed breakdown of a lead’s score in a campaign. Shows ICP score, individual signal contributions with all multipliers (time decay, recency bonus, intent, topic alignment, connection degree), and the total computed score. Use this to understand why a lead scored high or low. #### Parameters [Section titled “Parameters”](#parameters) | Name | Type | Required | Default | Description | | ------------ | --------------------- | -------- | ---------- | --------------- | | `campaignId` | string | Yes | — | (min length: 1) | | `leadSource` | `global` \| `private` | No | `"global"` | | | `leadId` | string | Yes | — | (min length: 1) | *** ### list\_lead\_signals [Section titled “list\_lead\_signals”](#list_lead_signals) **Full name:** `campaignstack_list_lead_signals` leads:read Read-only Idempotent List recent signals detected for a lead — job changes, engagement activity, etc. Signals are profile changes or engagement events that affect scoring. Sorted by detection time, newest first. #### Parameters [Section titled “Parameters”](#parameters-1) | Name | Type | Required | Default | Description | | ------------ | --------------------- | -------- | ---------- | ----------------- | | `leadSource` | `global` \| `private` | No | `"global"` | | | `leadId` | string | Yes | — | (min length: 1) | | `limit` | integer | No | `20` | (min: 1, max: 50) | | `cursor` | number | No | — | | *** ### get\_campaign\_weights [Section titled “get\_campaign\_weights”](#get_campaign_weights) **Full name:** `campaignstack_get_campaign_weights` campaigns:read Read-only Idempotent Get signal weight configuration for a campaign. Shows all signal types with their score impact, whether they’re enabled, and whether the weight is custom or default. Use with campaignstack\_update\_campaign\_weight to tune scoring. #### Parameters [Section titled “Parameters”](#parameters-2) | Name | Type | Required | Default | Description | | ------------ | ------ | -------- | ------- | --------------- | | `campaignId` | string | Yes | — | (min length: 1) | *** ### update\_campaign\_weight [Section titled “update\_campaign\_weight”](#update_campaign_weight) **Full name:** `campaignstack_update_campaign_weight` campaigns:write Idempotent Update the weight (score impact) of a signal type for a campaign. Use campaignstack\_get\_campaign\_weights to see current weights. #### Parameters [Section titled “Parameters”](#parameters-3) | Name | Type | Required | Default | Description | | ------------- | ------ | -------- | ------- | --------------------- | | `campaignId` | string | Yes | — | (min length: 1) | | `signalType` | string | Yes | — | (min length: 1) | | `scoreImpact` | number | Yes | — | (min: -100, max: 100) | *** # Topics & Search Watches > Inspect a campaign's topics and the workspace's active LinkedIn search watches, and create new manual search watches that source leads. Inspect a campaign’s topics and the workspace’s active LinkedIn search watches, and create new manual search watches that source leads. ## Tools (3) [Section titled “Tools (3)”](#tools-3) ### list\_search\_topics [Section titled “list\_search\_topics”](#list_search_topics) **Full name:** `campaignstack_list_search_topics` campaigns:read Read-only Idempotent List all active LinkedIn search-watch topics across campaigns in a workspace. Returns each enabled topic search automation with the topic ID and the campaign it belongs to. Use this to see which topics are already being monitored before creating a new search watch. To get topics assigned to a specific campaign, use campaignstack\_get\_campaign\_topics. #### Parameters [Section titled “Parameters”](#parameters) | Name | Type | Required | Default | Description | | ------------- | ------ | -------- | ------- | ----------------------------------- | | `workspaceId` | string | No | — | Defaults to the API key’s workspace | *** ### get\_campaign\_topics [Section titled “get\_campaign\_topics”](#get_campaign_topics) **Full name:** `campaignstack_get_campaign_topics` campaigns:read Read-only Idempotent Get all topics assigned to a campaign along with their weight (0–1). Topics with weight > 0.8 have active LinkedIn search watch automations. Returns topic metadata (id, name, displayName, category) and weight for each entry. If the campaign is not found, use campaignstack\_list\_campaigns to find valid campaign IDs. #### Parameters [Section titled “Parameters”](#parameters-1) | Name | Type | Required | Default | Description | | ------------ | ------ | -------- | ------- | --------------- | | `campaignId` | string | Yes | — | (min length: 1) | *** ### create\_search\_watch [Section titled “create\_search\_watch”](#create_search_watch) **Full name:** `campaignstack_create_search_watch` campaigns:write Set up a LinkedIn search watch that automatically discovers new leads for a campaign. A search watch monitors a topic keyword on LinkedIn content search (past 24h, relevance-sorted) every \~6 hours, extracting post authors and feeding them into the campaign as new leads. Only one search watch per topic is active across all campaigns in a workspace at a time — if another campaign already watches this topic, the new watch is created but starts disabled (ownership transfers automatically when the other campaign is archived). Use campaignstack\_list\_search\_topics to see what topics are already watched, and campaignstack\_get\_campaign\_topics to see topics already on the target campaign. #### Parameters [Section titled “Parameters”](#parameters-2) | Name | Type | Required | Default | Description | | ------------ | --------- | -------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `campaignId` | string | Yes | — | (min length: 1) | | `topicId` | string | Yes | — | ID of the topic to watch. Use campaignstack\_get\_campaign\_topics to see topics already on a campaign, or list taxonomy topics via campaignstack\_list\_search\_topics. (min length: 1) | | `accountIds` | string\[] | No | — | Optional list of LinkedIn account IDs to scope the search watch to. When omitted, the watch runs on any available account. | *** # Webhook Templates > List pre-configured webhook templates for integration nodes — Slack, Instantly, and more. List pre-configured webhook templates for integration nodes — Slack, Instantly, and more. ## Tools (1) [Section titled “Tools (1)”](#tools-1) ### list\_webhook\_templates [Section titled “list\_webhook\_templates”](#list_webhook_templates) **Full name:** `campaignstack_list_webhook_templates` workflows:read Read-only Idempotent List available webhook templates for integration:webhook nodes. Each template pre-configures URL, method, headers, and body for a specific service (Slack, Instantly, etc.). Use the returned templateId and templateFields when creating webhook nodes via campaignstack\_auto\_generate\_workflow. Templates with secretFields require the corresponding integration to be connected in workspace settings. *** # Workflow Execution > Monitor workflow progress — node stats, lead positions, activity logs, and retry failed entries. Monitor workflow progress — node stats, lead positions, activity logs, and retry failed entries. ## Tools (4) [Section titled “Tools (4)”](#tools-4) ### get\_workflow\_stats [Section titled “get\_workflow\_stats”](#get_workflow_stats) **Full name:** `campaignstack_get_workflow_stats` workflows:read Read-only Idempotent Get real-time execution statistics for a workflow. Returns per-node lead counts (waiting, processing, completed, failed), edge traversal counts, and paused action types. Use campaignstack\_get\_workflow to get the workflow structure first. #### Parameters [Section titled “Parameters”](#parameters) | Name | Type | Required | Default | Description | | ------------ | ------ | -------- | ------- | --------------- | | `workflowId` | string | Yes | — | (min length: 1) | *** ### list\_leads\_at\_node [Section titled “list\_leads\_at\_node”](#list_leads_at_node) **Full name:** `campaignstack_list_leads_at_node` workflows:read Read-only Idempotent List leads currently at a specific workflow node. Returns up to 100 leads with their name, headline, company, status, and timestamps. Entries with status ‘cancelled’ were terminated by the lead router (cancelReason: superseded\_by\_reply, superseded\_by\_newer, meeting\_booked, stale\_expired, workflow\_disabled). Use campaignstack\_get\_workflow\_stats to identify nodes of interest. #### Parameters [Section titled “Parameters”](#parameters-1) | Name | Type | Required | Default | Description | | -------- | ------ | -------- | ------- | --------------- | | `nodeId` | string | Yes | — | (min length: 1) | *** ### get\_node\_activity [Section titled “get\_node\_activity”](#get_node_activity) **Full name:** `campaignstack_get_node_activity` workflows:read Read-only Idempotent Get recent activity for a specific workflow node. Returns entries sorted by most recent, with per-status counts. Useful for monitoring execution progress and identifying failures. ‘cancelled’ entries carry a cancelReason explaining why the lead router terminated them (e.g. superseded\_by\_reply = the lead replied before the queued touch was sent). #### Parameters [Section titled “Parameters”](#parameters-2) | Name | Type | Required | Default | Description | | -------- | ------- | -------- | ------- | ------------------ | | `nodeId` | string | Yes | — | (min length: 1) | | `limit` | integer | No | `50` | (min: 1, max: 100) | *** ### retry\_failed\_entry [Section titled “retry\_failed\_entry”](#retry_failed_entry) **Full name:** `campaignstack_retry_failed_entry` workflows:write Idempotent Retry a failed workflow entry. Resets the entry from FAILED to WAITING so the workflow executor picks it up on the next tick. Use campaignstack\_list\_leads\_at\_node to find failed entries. #### Parameters [Section titled “Parameters”](#parameters-3) | Name | Type | Required | Default | Description | | ------------- | ------ | -------- | ------- | --------------- | | `entryId` | string | Yes | — | (min length: 1) | | `workspaceId` | string | Yes | — | (min length: 1) | *** # Workflow Node Vocabulary > Complete reference for all workflow node types, graph shape, and validation rules for campaignstack_create_workflow and campaignstack_update_workflow. Complete vocabulary of workflow node types for building automation graphs. Use `campaignstack_get_workflow_node_types` to fetch this data at runtime, or pass a `graph` object to `campaignstack_create_workflow` / `campaignstack_update_workflow`. ## Graph Shape [Section titled “Graph Shape”](#graph-shape) A workflow graph is { nodes, edges }. nodes is an ordered array of WorkflowNode objects. edges connect nodes by their 0-based array indices. The source:entry anchor node is always auto-created — do not include it in nodes. Use sourceConfig to configure lead sources (providers, dailyCap, exclusions). ### Node fields [Section titled “Node fields”](#node-fields) `\{ nodeType: string, label: string, config: object \} — position is auto-computed and can be omitted` ### Edge fields [Section titled “Edge fields”](#edge-fields) `\{ sourceIndex: number, targetIndex: number, label?: string \} — label is required for branching nodes (e.g. 'true'/'false' for flow:if, 'accepted'/'timeout' for linkedin:reactive with connection_accepted)` ## Minimal Example Graph [Section titled “Minimal Example Graph”](#minimal-example-graph) ```json { "nodes": [ { "nodeType": "linkedin:profile_view", "label": "View Profile", "config": {} }, { "nodeType": "flow:wait", "label": "Wait 3 Days", "config": { "waitType": "duration", "durationMs": 259200000 } }, { "nodeType": "linkedin:connection_request", "label": "Send Connection Request", "config": { "noteMode": "none" } } ], "edges": [ { "sourceIndex": 0, "targetIndex": 1 }, { "sourceIndex": 1, "targetIndex": 2 } ] } ``` ## Validation Rules [Section titled “Validation Rules”](#validation-rules) Rules enforced by `campaignstack_create_workflow` and `campaignstack_update_workflow`: * **At least one node** — the graph must contain at least one node. * **No source:entry in nodes** — the source:entry anchor node is always auto-created from `sourceConfig`. Do not include it in the `nodes` array. * **No signal:intake or signal:message in nodes** — these anchor nodes are preserved automatically in signal-agent workflows. Connect them to your nodes via `anchorEdges` instead. * **Valid node types** — every `nodeType` must exist in the vocabulary below. * **Label required** — every node must have a non-empty string `label`. * **Config required** — every node must have a `config` object (use `\{\}` for nodes with no config). * **Valid edge indices** — edge `sourceIndex` and `targetIndex` must be valid 0-based indices into the `nodes` array. No self-loops. * **flow:if edges** — all outgoing edges from a `flow:if` node must have `label: "true"` or `label: "false"`. * **Multi-exit edges** — outgoing edges from branching nodes (e.g. `review:content`, `signal:message`, `linkedin:reactive`) must use the node’s declared exit labels (see each node’s exits field below). * **Single-exit nodes** — all other nodes may have at most 1 outgoing edge. * **Auto-fixes** — `flow:wait` without `durationMs` defaults to 86 400 000 ms (1 day); `linkedin:reactive` with an invalid `timeoutDuration` defaults to 168 h; `linkedin:connection_request` with `noteMode: "ai"` is silently downgraded to `"none"`. * **anchorEdges** — for signal-agent graphs, `anchorEdges` connect the preserved anchor node to your graph. Each must have a valid `targetIndex` and a `label` in `["dm", "cr", "reply", "liked", "no_action"]`. ## Node Types (31 types) [Section titled “Node Types (31 types)”](#node-types-31-types) ### linkedin:profile\_view [Section titled “linkedin:profile\_view”](#linkedinprofile_view) **Label:** Profile View | **Category:** linkedin | **Credit cost:** 10 cr Views the lead’s LinkedIn profile to trigger a notification **Config:** ```ts {} ``` **Example:** ```json { "nodeType": "linkedin:profile_view", "label": "View Profile", "config": {} } ``` *** ### linkedin:post\_like [Section titled “linkedin:post\_like”](#linkedinpost_like) **Label:** Post Like | **Category:** linkedin | **Credit cost:** 10 cr Finds and likes the lead’s most recent LinkedIn post **Config:** ```ts {} ``` **Example:** ```json { "nodeType": "linkedin:post_like", "label": "Like Post", "config": {} } ``` *** ### linkedin:post\_comment [Section titled “linkedin:post\_comment”](#linkedinpost_comment) **Label:** Post Comment | **Category:** linkedin | **Credit cost:** 10 cr Reads the lead’s latest post and leaves a relevant comment **Config:** ```ts { commentText?: string } ``` **Example:** ```json { "nodeType": "linkedin:post_comment", "label": "Comment on Post", "config": {} } ``` *** ### linkedin:reply\_comment [Section titled “linkedin:reply\_comment”](#linkedinreply_comment) **Label:** Reply to Comment | **Category:** linkedin | **Credit cost:** 10 cr Replies to a specific comment on a LinkedIn post — navigates to the post, finds the comment by author name and text, and posts the approved reply **Config:** ```ts {} ``` **Example:** ```json { "nodeType": "linkedin:reply_comment", "label": "Reply to Comment", "config": {} } ``` *** ### linkedin:connection\_request [Section titled “linkedin:connection\_request”](#linkedinconnection_request) **Label:** Connection Request | **Category:** linkedin | **Credit cost:** 10 cr Sends a connection request. Choose between AI-personalized, template, or no note **Config:** ```ts { noteMode: "none"|"static", noteTemplate?: string } — for a personalized note, build ai:craft_note → review:content → linkedin:connection_request { noteMode: "static" } (the note flows from context). The connection_request node does not self-generate AI notes. ``` **Example:** ```json { "nodeType": "linkedin:connection_request", "label": "Send Connection Request", "config": { "noteMode": "none" } } ``` *** ### linkedin:send\_message [Section titled “linkedin:send\_message”](#linkedinsend_message) **Label:** Send Message | **Category:** linkedin | **Credit cost:** 10 cr Sends a direct message to the lead. Supports AI-generated or template text **Config:** ```ts { messageTemplate?: string, useAi?: boolean, attachmentStorageId?: Id<"_storage"> } — send literal messageTemplate or approved upstream ai:craft_message content. Do not generate only { useAi: true }; for automatic personalized DMs use linkedin:research_and_message. ``` **Example:** ```json { "nodeType": "linkedin:send_message", "label": "Send Message", "config": { "messageTemplate": "Hi {{lead:firstName}}, I noticed your work at {{lead:company}} and wanted to connect." } } ``` *** ### linkedin:send\_followup [Section titled “linkedin:send\_followup”](#linkedinsend_followup) **Label:** Send Follow-up | **Category:** linkedin | **Credit cost:** 10 cr Sends a follow-up message in an existing conversation thread **Config:** ```ts { messageTemplate?: string, useAi?: boolean } — send follow-up in existing conversation thread. Requires a prior send_message or research_and_message node upstream so a conversation exists. useAi=true calls craft_followup for personalized text. ``` **Example:** ```json { "nodeType": "linkedin:send_followup", "label": "Send Follow-up", "config": { "useAi": true } } ``` *** ### linkedin:inbox\_read [Section titled “linkedin:inbox\_read”](#linkedininbox_read) **Label:** Inbox Read | **Category:** linkedin | **Credit cost:** 0 cr Reads recent messages from a conversation with the lead **Config:** ```ts { pollIntervalMs?: number (in milliseconds, e.g. 60000 = 1 minute) } ``` **Example:** ```json { "nodeType": "linkedin:inbox_read", "label": "Read Inbox", "config": {} } ``` *** ### linkedin:research\_and\_message [Section titled “linkedin:research\_and\_message”](#linkedinresearch_and_message) **Label:** Research & Message | **Category:** linkedin | **Credit cost:** 15 cr Researches the lead’s profile and posts, then sends a personalized message based on findings **Config:** ```ts { messageInstructions?: string, maxPosts?: number } ``` **Example:** ```json { "nodeType": "linkedin:research_and_message", "label": "Research & Message", "config": { "messageInstructions": "Focus on their recent posts and company news." } } ``` *** ### signal:intake [Section titled “signal:intake”](#signalintake) **Label:** Signal Intake | **Category:** signal | **Credit cost:** 15 cr Decides the best action for a signal (DM, CR, reply, like, or skip) and crafts content — downstream nodes handle actual sending > **Note:** This node type is not available in the visual workflow builder. It is used internally by signal-agent workflows. **Config:** ```ts { crNoteMode?: "none"|"static"|"with_note", crNoteTemplate?: string, reviewMode?: "on"|"off", publicReplyEnabled?: boolean, allowConnectionRequest?: boolean } — signal-agent only. Exits: dm, cr, liked, no_action, plus reply when publicReplyEnabled is true. allowConnectionRequest: false downgrades would-be CRs to liked. ``` **Example:** ```json { "nodeType": "signal:intake", "label": "Signal Intake", "config": { "crNoteMode": "none", "reviewMode": "off" } } ``` *** ### signal:message [Section titled “signal:message”](#signalmessage) **Label:** Message Signal Intake | **Category:** signal | **Credit cost:** 10 cr Receives an inbound LinkedIn message, decides the best response, and crafts a reply — downstream nodes handle review and sending **Config:** ```ts { reviewMode: "on"|"off", handleInMail?: boolean } — message signal agent only. Exits: respond, meeting_scheduled (inbound message carried a calendar link and a meeting was booked), escalate. ``` **Exits:** `respond`, `meeting_scheduled`, `escalate` **Example:** ```json { "nodeType": "signal:message", "label": "Message Signal Intake", "config": { "reviewMode": "off" } } ``` *** ### group:intake [Section titled “group:intake”](#groupintake) **Label:** Group Intake | **Category:** group | **Credit cost:** 0 cr Entry point for group watch workflows — routes ICP-matching group members to the outreach chain. Optionally parks leads for human review before outreach. > **Note:** This node type is not available in the visual workflow builder. It is used internally by signal-agent workflows. **Config:** ```ts { reviewMode?: "on"|"off" } ``` **Example:** ```json { "nodeType": "group:intake", "label": "Group Intake", "config": { "reviewMode": "off" } } ``` *** ### connection:intake [Section titled “connection:intake”](#connectionintake) **Label:** Connection Intake | **Category:** connection | **Credit cost:** 0 cr Entry point for connection-watch workflows — receives newly accepted connections and routes them to the welcome chain (craft → review → send) > **Note:** This node type is not available in the visual workflow builder. It is used internally by signal-agent workflows. **Config:** ```ts { reviewMode?: "on"|"off" } — connection-watch signal agent only. The review gate lives on the crafted message, not the intake. ``` **Example:** ```json { "nodeType": "connection:intake", "label": "Connection Intake", "config": { "reviewMode": "off" } } ``` *** ### linkedin:contact\_info [Section titled “linkedin:contact\_info”](#linkedincontact_info) **Label:** Extract Contact Info | **Category:** linkedin | **Credit cost:** 10 cr Extracts email, phone, and website from LinkedIn Contact Info overlay. 1st-degree connections only. **Config:** ```ts {} ``` **Example:** ```json { "nodeType": "linkedin:contact_info", "label": "Extract Contact Info", "config": {} } ``` *** ### linkedin:reactive [Section titled “linkedin:reactive”](#linkedinreactive) **Label:** Reactive Node | **Category:** linkedin | **Credit cost:** 0 cr Parks the lead and waits for a LinkedIn event (e.g. message reply, connection acceptance) before continuing **Config:** ```ts { eventType: "message_received"|"connection_accepted", timeoutDuration: number (in hours, e.g. 168 = 7 days, 336 = 14 days; valid range: 1-720), responseMode?: "auto_respond"|"draft_and_review", autoBooking?: boolean } — reply and escalation are always enabled. autoBooking: when the lead sends a scheduling link, book a mutual slot via the account’s connected calendar (auto_respond only; needs a calendar-connected account). Escalation parks the lead (creates a pending action, no exit). connection_accepted exits: accepted, timeout. message_received + draft_and_review exits: draft_ready, timeout. message_received + auto_respond exits: replied, timeout (+ meeting_scheduled when autoBooking enabled). ``` **Example:** ```json { "nodeType": "linkedin:reactive", "label": "Wait for Reply", "config": { "eventType": "message_received", "timeoutDuration": 168 } } ``` *** ### linkedin:reactive\_respond [Section titled “linkedin:reactive\_respond”](#linkedinreactive_respond) **Label:** Reactive Responder | **Category:** linkedin | **Credit cost:** 10 cr Browser-based agent that reads a new LinkedIn message and responds using configured tools. Routes to sent\_message, needs\_human, or no\_action based on which tool was used. **Config:** ```ts { instructions?: string, enabledTools?: string[] } — runner responder used after a reactive message event. ``` **Example:** ```json { "nodeType": "linkedin:reactive_respond", "label": "Respond to Reply", "config": { "instructions": "Be helpful and professional." } } ``` *** ### google:email\_send [Section titled “google:email\_send”](#googleemail_send) **Label:** Send Email | **Category:** google | **Credit cost:** 0 cr Sends an email via Gmail. Static mode resolves {{lead:\*}} template variables. Pair with ai:craft\_email for AI-personalized copy. **Config:** ```ts { mode?: "static"|"crafted"|"auto_generate", googleAccountId?: string, subject?: string, bodyTemplate?: string, htmlBodyTemplate?: string, attachmentIds?: string[], autoGenerateInstructions?: string, blockOnFailure?: boolean } — static mode resolves {{lead:firstName}}, {{lead:company}}, etc. in subject/body. crafted mode reads upstream craftedSubject/craftedBody from an ai:craft_email node. auto_generate mode generates subject+body via AI in one step (15 credits, same as ai:craft_email); use autoGenerateInstructions to guide the AI. Fails if lead has no email address. Best practices: use static mode for low-context leads (name + email only, no profile data). Keep body under 80 words, subject 2-4 words lowercase, zero selling language (no "free", "guaranteed", "exclusive"). For multi-email sequences, place a flow:wait { waitType: "email_event", emailEventType: "click", timeoutHours: 96 } after each send so leads who click exit the sequence and only non-engagers receive the next email. Each email in a sequence should target a separate pain point rather than repeating the same pitch. blockOnFailure defaults to true on the backend. ``` **Example:** ```json { "nodeType": "google:email_send", "label": "Send Email", "config": { "mode": "crafted" } } ``` *** ### ai:autonomous [Section titled “ai:autonomous”](#aiautonomous) **Label:** Autonomous Agent | **Category:** ai | **Credit cost:** 10 cr Executes custom instructions using any combination of LinkedIn actions in a single session **Config:** ```ts { instructions: string } ``` **Example:** ```json { "nodeType": "ai:autonomous", "label": "Autonomous Agent", "config": { "instructions": "Research the lead's profile and send a personalized message referencing their recent activity." } } ``` *** ### ai:craft\_note [Section titled “ai:craft\_note”](#aicraft_note) **Label:** Craft Connection Note | **Category:** ai | **Credit cost:** 5 cr Generates a personalized connection request note (max 300 chars) **Config:** ```ts { instructions?: string } ``` **Example:** ```json { "nodeType": "ai:craft_note", "label": "Craft Connection Note", "config": {} } ``` *** ### ai:craft\_message [Section titled “ai:craft\_message”](#aicraft_message) **Label:** Craft Message | **Category:** ai | **Credit cost:** 5 cr Generates a personalized outreach message based on lead profile and campaign goals **Config:** ```ts { instructions?: string, messageTemplate?: string } ``` **Example:** ```json { "nodeType": "ai:craft_message", "label": "Craft Message", "config": {} } ``` *** ### ai:craft\_comment [Section titled “ai:craft\_comment”](#aicraft_comment) **Label:** Craft Comment | **Category:** ai | **Credit cost:** 5 cr Generates a relevant comment for a lead’s latest post **Config:** ```ts { instructions?: string } ``` **Example:** ```json { "nodeType": "ai:craft_comment", "label": "Craft Comment", "config": {} } ``` *** ### ai:reply\_conversation [Section titled “ai:reply\_conversation”](#aireply_conversation) **Label:** Reply to Conversation | **Category:** ai | **Credit cost:** 5 cr Generates a contextual follow-up reply based on conversation history **Config:** ```ts { instructions?: string } ``` **Example:** ```json { "nodeType": "ai:reply_conversation", "label": "Reply to Conversation", "config": {} } ``` *** ### ai:craft\_email [Section titled “ai:craft\_email”](#aicraft_email) **Label:** Craft Email | **Category:** ai | **Credit cost:** 15 cr Generates a personalized email (subject + plain text body, with optional HTML) based on lead profile, campaign goals, and workspace voice **Config:** ```ts { instructions?: string, tone?: string, includeSignature?: boolean } — generates craftedSubject + craftedBody (plain text) + craftedHtmlBody (HTML, only when instructions or voice mention HTML/formatting). Pair with a google:email_send node to deliver the email. Use crafted mode when leads have rich profile data (headline, company, experience) worth personalizing against. For leads with only name + email (no profile context), static mode on google:email_send is better because AI will produce nearly identical generic emails and waste credits. ``` **Example:** ```json { "nodeType": "ai:craft_email", "label": "Craft Email", "config": { "tone": "professional", "includeSignature": true } } ``` *** ### review:content [Section titled “review:content”](#reviewcontent) **Label:** Review Content | **Category:** review | **Credit cost:** 0 cr Pauses workflow for human review of AI-generated content. Supports approve, edit, retry, and reject actions. **Config:** ```ts { contentNodeId?: string } — optional reference to upstream ai:* node (auto-detected if omitted) ``` **Exits:** `rejected`, `approved` **Example:** ```json { "nodeType": "review:content", "label": "Review Content", "config": {} } ``` *** ### flow:wait [Section titled “flow:wait”](#flowwait) **Label:** Wait | **Category:** flow | **Credit cost:** 0 cr Pause lead progression for a configurable duration with jitter, or wait for an email open/click event **Config:** ```ts { waitType?: "duration"|"email_event", durationMs?: number (ms, e.g. 86400000 = 1 day — required for duration mode), emailEventType?: "open"|"click"|"any" (default "any" — for email_event mode), timeoutHours?: number (default 72 — timeout before advancing) } — duration mode waits a fixed time (single exit). email_event mode has TWO exits: "triggered" (lead opened/clicked the email) and "timeout" (no engagement within timeoutHours). Pair email_event mode with google:email_send upstream. ``` **Example:** ```json { "nodeType": "flow:wait", "label": "Wait 3 Days", "config": { "waitType": "duration", "durationMs": 259200000 } } ``` *** ### flow:if [Section titled “flow:if”](#flowif) **Label:** If / Condition | **Category:** flow | **Credit cost:** 0 cr One-shot agent check of a condition (e.g., connection accepted, message replied) **Config:** ```ts { conditionType: "connection_accepted"|"message_replied"|"custom", expression?: string } ``` **Exits:** `false`, `true` **Example:** ```json { "nodeType": "flow:if", "label": "Check Connection Accepted", "config": { "conditionType": "connection_accepted" } } ``` *** ### flow:filter [Section titled “flow:filter”](#flowfilter) **Label:** Filter | **Category:** flow | **Credit cost:** 0 cr Gate lead flow based on rules (e.g., ICP score threshold, profile fields) **Config:** ```ts { rules: Array<{ field, operator: "equals"|"contains"|"gt"|"lt"|"exists"|"not_exists", value }>, logic: "and"|"or" } ``` **Example:** ```json { "nodeType": "flow:filter", "label": "Filter by ICP Score", "config": { "rules": [ { "field": "icpScore", "operator": "gt", "value": 50 } ], "logic": "and" } } ``` *** ### enrich:wait [Section titled “enrich:wait”](#enrichwait) **Label:** Wait for Enrichment | **Category:** enrich | **Credit cost:** 0 cr Park lead until profile enrichment completes, then proceed. Skips instantly if lead already has enrichment data. > **Note:** This node type is not available in the visual workflow builder. It is used internally by signal-agent workflows. **Config:** ```ts { timeoutMinutes?: number (default 30) } — waits for lead enrichment to complete. Exits immediately if lead already has enrichment data. Timeout exit proceeds with whatever data is available. ``` **Exits:** `enriched`, `timeout` **Example:** ```json { "nodeType": "enrich:wait", "label": "Wait for Enrichment", "config": { "timeoutMinutes": 30 } } ``` *** ### flow:split [Section titled “flow:split”](#flowsplit) **Label:** Split | **Category:** flow | **Credit cost:** 0 cr Distribute leads across 2-5 weighted branches for A/B testing. Assignment is deterministic — the same lead always takes the same branch. **Config:** ```ts { branches: Array<{ label: string, weight: number }>, autoOptimize?: boolean, minLeadsPerBranch?: number } — 2 to 5 branches, weights must sum to 100. Exit handle IDs are branch indexes as strings ("0", "1", ...). When autoOptimize is true, a daily cron re-weights branches via Thompson Sampling on downstream positive exits once every branch has at least minLeadsPerBranch leads (default 20); lastOptimizedAt is set by the optimizer, do not set it manually. ``` **Example:** ```json { "nodeType": "flow:split", "label": "A/B Split", "config": { "branches": [ { "label": "A", "weight": 50 }, { "label": "B", "weight": 50 } ] } } ``` *** ### notify:user [Section titled “notify:user”](#notifyuser) **Label:** Notify Me | **Category:** notify | **Credit cost:** 0 cr Sends a notification to workspace members (in-app + email per their preferences) when a lead reaches this step — the message goes to YOU, never to the lead **Config:** ```ts { subject: string, message: string } — both support {{variable}} interpolation: lead.firstName, lead.lastName, lead.company, lead.title, campaign.name, workflow.name, node.label. Unknown variables render empty. Recipients follow the workspace's workflow-notification audience setting (default: all members); there is no per-node recipient picker. Single exit, zero credits, never budget-gated. ``` **Example:** ```json { "nodeType": "notify:user", "label": "Notify Me", "config": { "subject": "{{lead.firstName}} {{lead.lastName}} reached {{node.label}}", "message": "{{lead.firstName}} {{lead.lastName}} ({{lead.title}} at {{lead.company}}) just reached {{node.label}} in {{workflow.name}}." } } ``` *** ### integration:webhook [Section titled “integration:webhook”](#integrationwebhook) **Label:** Webhook | **Category:** integration | **Credit cost:** 0 cr Sends lead data to an HTTP endpoint or pre-configured service (Slack, Instantly, etc.). Select a template for quick setup or use custom config with POST/PUT/PATCH and template variables. **Config:** ```ts { url: string, method?: "POST"|"PUT"|"PATCH", headers?: Array<{ key, value }>, bodyTemplate?: string, blockOnFailure?: boolean, timeoutMs?: number } for custom webhooks, OR use a template: { templateId: "slack", templateFields: { webhookUrl: "https://hooks.slack.com/...", messageTemplate?: "mrkdwn text with {{lead:fullName}} etc." }, blockOnFailure?: boolean } or { templateId: "instantly-add-lead", templateFields: { campaignId: "uuid" }, blockOnFailure?: false }. Templates handle URL, method, headers, and body format automatically. ``` **Example:** ```json { "nodeType": "integration:webhook", "label": "Notify Slack", "config": { "templateId": "slack", "templateFields": { "webhookUrl": "https://hooks.slack.com/services/YOUR/WEBHOOK/URL", "messageTemplate": "New lead: {{lead:fullName}} from {{lead:company}}" } } } ``` *** # Workflow Nodes > Internal tools for completing, failing, and managing workflow node execution. Internal tools for completing, failing, and managing workflow node execution. ## Tools (5) [Section titled “Tools (5)”](#tools-5) ### complete\_node [Section titled “complete\_node”](#complete_node) **Full name:** `campaignstack_complete_node` workflows:write Idempotent Report successful completion of a workflow node for a lead. Marks the lead entry as COMPLETED, pushes the lead to the next node(s) via edges, and logs the action for audit. Returns the IDs of next nodes the lead was pushed to. #### Parameters [Section titled “Parameters”](#parameters) | Name | Type | Required | Default | Description | | --------- | ------ | -------- | ------- | --------------- | | `entryId` | string | Yes | — | (min length: 1) | | `payload` | object | No | — | | *** ### fail\_node [Section titled “fail\_node”](#fail_node) **Full name:** `campaignstack_fail_node` workflows:write Idempotent Report failure of a workflow node for a lead. Marks the lead entry as FAILED with an error message. For auth\_expired: reverts lead to WAITING and marks LinkedIn account as expired. For rate\_limit: auto-pauses the action type with a 1-hour cooldown. Use campaignstack\_complete\_node for successful completions. #### Parameters [Section titled “Parameters”](#parameters-1) | Name | Type | Required | Default | Description | | ----------- | -------------------------------------------------------------- | -------- | ------- | --------------- | | `entryId` | string | Yes | — | (min length: 1) | | `error` | string | Yes | — | (min length: 1) | | `errorType` | `auth_expired` \| `rate_limit` \| `not_connected` \| `generic` | No | — | | *** ### store\_conversation [Section titled “store\_conversation”](#store_conversation) **Full name:** `campaignstack_store_conversation` workflows:write Idempotent Store sent/received LinkedIn messages in the linkedinConversations table. Deduplicates against existing messages (same senderProfileUrn + body within 60s = skip). Creates a new conversation record if none exists for the accountId + leadId pair. Returns the number of messages stored and skipped. #### Parameters [Section titled “Parameters”](#parameters-2) | Name | Type | Required | Default | Description | | ------------ | --------------------- | -------- | ------- | --------------------------------------------------------------------------------------------------------------- | | `leadId` | string | Yes | — | (min length: 1) | | `leadSource` | `global` \| `private` | No | — | Defaults to ‘global’. Pass ‘private’ for workspace-private leads so the conversation links to the right record. | | `accountId` | string | Yes | — | (min length: 1) | | `threadId` | string | No | — | | | `messages` | object\[] | Yes | — | | *** ### escalate\_to\_human [Section titled “escalate\_to\_human”](#escalate_to_human) **Full name:** `campaignstack_escalate_to_human` workflows:write Idempotent Escalate a lead to human review from a reactive-responder agent. Creates a pending\_review notification in the workspace so a team member can follow up. The lead stays parked at the reactive node — do NOT reply when escalating (they are mutually exclusive). Use this when the conversation requires human judgement (e.g. pricing questions, sensitive topics, complex objections). #### Parameters [Section titled “Parameters”](#parameters-3) | Name | Type | Required | Default | Description | | ------------ | ------ | -------- | ------- | --------------- | | `nodeId` | string | Yes | — | (min length: 1) | | `workflowId` | string | Yes | — | (min length: 1) | | `leadId` | string | Yes | — | (min length: 1) | | `reason` | string | No | — | | *** ### extract\_post\_engagements [Section titled “extract\_post\_engagements”](#extract_post_engagements) **Full name:** `campaignstack_extract_post_engagements` content:write Extract all engagement data (reactions, comments, reposts) from a LinkedIn post. Must be called AFTER using browser tools to click reactions count, repost count, and sorting comments to ‘Most Recent’. The extraction reads intercepted API data from the current browser session via Voyager GraphQL. Returns counts of extracted reactions, comments, and reposts. #### Parameters [Section titled “Parameters”](#parameters-4) | Name | Type | Required | Default | Description | | ---------------- | ------ | -------- | ------- | --------------- | | `postUrl` | string | Yes | — | (min length: 1) | | `authorName` | string | No | — | | | `contentSnippet` | string | No | — | | *** # Workflow Review > Human-in-the-loop review — list, approve, reject, edit, and retry pending workflow actions. Human-in-the-loop review — list, approve, reject, edit, and retry pending workflow actions. ## Tools (6) [Section titled “Tools (6)”](#tools-6) ### list\_pending\_reviews [Section titled “list\_pending\_reviews”](#list_pending_reviews) **Full name:** `campaignstack_list_pending_reviews` workflows:read Read-only Idempotent List pending workflow review actions requiring human approval. Returns entries with lead details (including leadLinkedInUrl), crafted content, campaign/workflow context, and a reviewContext discriminated union: type “post\_comment” includes commentText, postContent, postUrl, postAuthorName (when triggered by a post comment signal); type “campaign” for regular campaign workflow reviews; null when context cannot be determined. Use campaignstack\_approve\_review, campaignstack\_reject\_review, or campaignstack\_edit\_and\_approve\_review to act on entries. #### Parameters [Section titled “Parameters”](#parameters) | Name | Type | Required | Default | Description | | ------------- | ------- | -------- | ------- | ------------------ | | `workspaceId` | string | Yes | — | (min length: 1) | | `limit` | integer | No | `20` | (min: 1, max: 100) | | `cursor` | string | No | — | | *** ### count\_pending\_reviews [Section titled “count\_pending\_reviews”](#count_pending_reviews) **Full name:** `campaignstack_count_pending_reviews` workflows:read Read-only Idempotent Count pending workflow review actions in a workspace. Returns the total number of entries awaiting human approval. #### Parameters [Section titled “Parameters”](#parameters-1) | Name | Type | Required | Default | Description | | ------------- | ------ | -------- | ------- | --------------- | | `workspaceId` | string | Yes | — | (min length: 1) | *** ### approve\_review [Section titled “approve\_review”](#approve_review) **Full name:** `campaignstack_approve_review` workflows:write Approve a pending review entry. The lead advances to the next workflow node via the ‘approved’ edge. Use campaignstack\_list\_pending\_reviews to find entry IDs. #### Parameters [Section titled “Parameters”](#parameters-2) | Name | Type | Required | Default | Description | | ------------- | ------ | -------- | ------- | --------------- | | `entryId` | string | Yes | — | (min length: 1) | | `workspaceId` | string | Yes | — | (min length: 1) | *** ### reject\_review [Section titled “reject\_review”](#reject_review) **Full name:** `campaignstack_reject_review` workflows:write Reject a pending review entry. The lead follows the ‘rejected’ edge in the workflow. #### Parameters [Section titled “Parameters”](#parameters-3) | Name | Type | Required | Default | Description | | ------------- | ------ | -------- | ------- | --------------- | | `entryId` | string | Yes | — | (min length: 1) | | `workspaceId` | string | Yes | — | (min length: 1) | *** ### edit\_and\_approve\_review [Section titled “edit\_and\_approve\_review”](#edit_and_approve_review) **Full name:** `campaignstack_edit_and_approve_review` workflows:write Edit the AI-generated content and approve the review entry. The original content is preserved for audit trail. The lead advances via the ‘approved’ edge. #### Parameters [Section titled “Parameters”](#parameters-4) | Name | Type | Required | Default | Description | | ---------------- | ------ | -------- | ------- | --------------- | | `entryId` | string | Yes | — | (min length: 1) | | `workspaceId` | string | Yes | — | (min length: 1) | | `updatedContent` | object | Yes | — | | *** ### retry\_review [Section titled “retry\_review”](#retry_review) **Full name:** `campaignstack_retry_review` workflows:write Retry AI content generation for a pending review entry. The entry returns to PROCESSING while the AI regenerates, then comes back to PENDING\_REVIEW with new content. #### Parameters [Section titled “Parameters”](#parameters-5) | Name | Type | Required | Default | Description | | ------------- | ------ | -------- | ------- | --------------- | | `entryId` | string | Yes | — | (min length: 1) | | `workspaceId` | string | Yes | — | (min length: 1) | *** # Workflows > Create, trigger, and manage automated outreach sequences. Create, trigger, and manage automated outreach sequences. ## Tools (8) [Section titled “Tools (8)”](#tools-8) ### list\_workflows [Section titled “list\_workflows”](#list_workflows) **Full name:** `campaignstack_list_workflows` workflows:read Read-only Idempotent List all workflows for a campaign. Returns workflow IDs, names, status, enabled flag, and node count. Use the returned workflowId values with campaignstack\_get\_workflow to inspect workflow details, or campaignstack\_trigger\_workflow to execute a workflow. #### Parameters [Section titled “Parameters”](#parameters) | Name | Type | Required | Default | Description | | ------------ | ------ | -------- | ------- | --------------- | | `campaignId` | string | Yes | — | (min length: 1) | *** ### get\_workflow [Section titled “get\_workflow”](#get_workflow) **Full name:** `campaignstack_get_workflow` workflows:read Read-only Idempotent Get a workflow by ID with all nodes (type, label, config, position, execution target) and edges. If the workflow is not found, use campaignstack\_list\_workflows to find valid workflow IDs. #### Parameters [Section titled “Parameters”](#parameters-1) | Name | Type | Required | Default | Description | | ------------ | ------ | -------- | ------- | --------------- | | `workflowId` | string | Yes | — | (min length: 1) | *** ### get\_workflow\_node\_types [Section titled “get\_workflow\_node\_types”](#get_workflow_node_types) **Full name:** `campaignstack_get_workflow_node_types` workflows:read Read-only Idempotent Returns the complete vocabulary of workflow node types available for building automation graphs. For each node type includes: nodeType key, label, description, configHint (exact TypeScript-style config shape), a concrete example node, exit handles for branching nodes, and credit cost per execution. Also returns graphShape documentation (how to structure nodes/edges) and a minimal end-to-end example graph. Use this before calling campaignstack\_create\_workflow to ensure you author a valid graph. *** ### create\_workflow [Section titled “create\_workflow”](#create_workflow) **Full name:** `campaignstack_create_workflow` workflows:write Create a new workflow from a graph you author. The graph defines automation nodes (e.g. linkedin:profile\_view, linkedin:connection\_request, flow:wait) and directed edges. Use campaignstack\_get\_workflow\_node\_types first to discover valid node types, their configHint shapes, and the required graph format. Returns { workflowId, nodeCount } on success. sourceConfig is optional — omit it to get a default campaign\_pool source with icpScoreMin 50. Signal-intake graphs (lead-list-based source providers) are not supported by this tool. #### Parameters [Section titled “Parameters”](#parameters-2) | Name | Type | Required | Default | Description | | -------------- | ------ | -------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `workspaceId` | string | No | — | Defaults to the API key’s workspace | | `campaignId` | string | No | — | Associate this workflow with a campaign | | `name` | string | Yes | — | (min length: 1) | | `graph` | object | Yes | — | Workflow graph: nodes (nodeType, label, config) + edges (sourceIndex, targetIndex). Use campaignstack\_get\_workflow\_node\_types to discover valid node types. | | `sourceConfig` | object | No | — | Source node configuration (providers format only). Omit to use the default: a single campaign\_pool provider with icpScoreMin 50. Signal-intake graphs using lead-list-based sources are not supported by this tool. | *** ### update\_workflow [Section titled “update\_workflow”](#update_workflow) **Full name:** `campaignstack_update_workflow` workflows:write Idempotent Update a workflow’s name, description, and/or graph. Provide name/description to patch metadata; provide graph to replace the non-anchor nodes and edges. Both can be supplied in a single call. Set existingNodeId on graph nodes that update an existing node in place — claimed nodes keep their in-flight leads, while removing a node that still holds active leads fails with NODE\_HAS\_ACTIVE\_LEADS (drain it first with campaignstack\_cancel\_node\_leads). Graph replacement runs validation — invalid graphs are rejected and the workflow is left unchanged. To enable or disable a workflow use campaignstack\_update\_workflow\_status instead. *** ### delete\_workflow [Section titled “delete\_workflow”](#delete_workflow) **Full name:** `campaignstack_delete_workflow` workflows:write Destructive Permanently delete a workflow and all associated data. This action is irreversible and cascades to: workflow nodes, edges, lead-list source providers, node lead-list entries (lead progress), per-lead workflow contexts, and webhook tokens tied to this workflow. Use campaignstack\_list\_workflows to find workflow IDs. If you want to stop a workflow without deleting it, use campaignstack\_update\_workflow\_status instead. #### Parameters [Section titled “Parameters”](#parameters-3) | Name | Type | Required | Default | Description | | ------------ | ------ | -------- | ------- | --------------- | | `workflowId` | string | Yes | — | (min length: 1) | *** ### update\_workflow\_status [Section titled “update\_workflow\_status”](#update_workflow_status) **Full name:** `campaignstack_update_workflow_status` workflows:write Idempotent Enable or disable a workflow. When enabled, the workflow will process leads according to its node configuration. Use campaignstack\_get\_workflow to inspect the current node configuration first. #### Parameters [Section titled “Parameters”](#parameters-4) | Name | Type | Required | Default | Description | | ------------ | ------- | -------- | ------- | --------------- | | `workflowId` | string | Yes | — | (min length: 1) | | `enabled` | boolean | Yes | — | | *** ### trigger\_workflow [Section titled “trigger\_workflow”](#trigger_workflow) **Full name:** `campaignstack_trigger_workflow` workflows:write Trigger workflow execution for specific leads or all leads in the workflow’s lead list. Leads are set to ‘waiting’ status at the workflow entry node and will be processed by the workflow executor. Use campaignstack\_list\_workflows to find workflow IDs, and campaignstack\_get\_lead\_list to see available leads. #### Parameters [Section titled “Parameters”](#parameters-5) | Name | Type | Required | Default | Description | | ------------ | --------- | -------- | ------- | --------------- | | `workflowId` | string | Yes | — | (min length: 1) | | `leadIds` | string\[] | No | — | | *** # Workspace > Workspace-level tools — identity, website analysis, and diagnostics. Workspace-level tools — identity, website analysis, and diagnostics. ## Tools (3) [Section titled “Tools (3)”](#tools-3) ### whoami [Section titled “whoami”](#whoami) **Full name:** `campaignstack_whoami` No scope required Read-only Idempotent Returns information about the current API key: workspace name, available scopes, and key prefix. Useful for debugging permission issues. *** ### analyze\_website [Section titled “analyze\_website”](#analyze_website) **Full name:** `campaignstack_analyze_website` campaigns:read Read-only Idempotent Analyze a website to extract structured company intelligence. Returns company name, description, services, target market, industry, team size, tech stack, and positioning. Results are cached for 7 days per domain. Use this to gather context about a lead’s company before creating campaigns. #### Parameters [Section titled “Parameters”](#parameters) | Name | Type | Required | Default | Description | | ----- | ------ | -------- | ------- | ----------- | | `url` | string | Yes | — | | *** ### diagnose\_lead\_sourcing [Section titled “diagnose\_lead\_sourcing”](#diagnose_lead_sourcing) **Full name:** `campaignstack_diagnose_lead_sourcing` leads:read Read-only Idempotent Diagnose why a specific lead was or wasn’t sourced into a workflow. Accepts a workflowId plus either a leadId or profileUrl (LinkedIn URL). Returns a diagnostic report covering: lead existence, ICP score, score threshold checks, dedup status (same-workflow and cross-workflow), exclusion list matches, per-provider filter evaluation, and a human-readable verdict explaining the sourcing outcome. Use campaignstack\_list\_workflows to find valid workflowId values. Use campaignstack\_search\_leads to find valid leadId values. *** # Playbook & Feedback > Read and regenerate the workspace's campaign playbook, and submit product feedback. Read and regenerate the workspace’s campaign playbook, and submit product feedback. ## Tools (3) [Section titled “Tools (3)”](#tools-3) ### get\_playbook [Section titled “get\_playbook”](#get_playbook) **Full name:** `campaignstack_get_playbook` workspace:read Read-only Idempotent Get the workspace’s campaign playbook — an AI-generated strategy document covering company overview, outreach goals, target audience, tone, key talking points, and message boundaries. Returns null if no playbook has been generated yet. Use campaignstack\_regenerate\_playbook to create or refresh it. #### Parameters [Section titled “Parameters”](#parameters) | Name | Type | Required | Default | Description | | ------------- | ------ | -------- | ------- | ----------------------------------- | | `workspaceId` | string | No | — | Defaults to the API key’s workspace | *** ### regenerate\_playbook [Section titled “regenerate\_playbook”](#regenerate_playbook) **Full name:** `campaignstack_regenerate_playbook` workspace:write Destructive Trigger AI regeneration of the workspace playbook. This overwrites any existing playbook (including manual edits) with a freshly generated one based on current campaigns, ICPs, personas, and company context. The operation is NOT idempotent and may take several seconds. Fetch the updated playbook afterwards with campaignstack\_get\_playbook. #### Parameters [Section titled “Parameters”](#parameters-1) | Name | Type | Required | Default | Description | | ------------- | ------ | -------- | ------- | ----------------------------------- | | `workspaceId` | string | No | — | Defaults to the API key’s workspace | *** ### submit\_feedback [Section titled “submit\_feedback”](#submit_feedback) **Full name:** `campaignstack_submit_feedback` workspace:write Submit a feedback report or bug report to the CampaignStack team. Mirrors the in-app feedback widget (feedback-widget.tsx) — writes to the same feedbackReports table and triggers the same email notification. Use category ‘bug’ for broken functionality, ‘feature’ for capability requests, ‘question’ for how-to questions, and ‘other’ for anything else. Returns the new feedback report ID. #### Parameters [Section titled “Parameters”](#parameters-2) | Name | Type | Required | Default | Description | | ------------- | ------------------------------------------- | -------- | ------- | ------------------------------------------------------------------------------------------------------------------- | | `workspaceId` | string | No | — | Defaults to the API key’s workspace | | `category` | `bug` \| `feature` \| `question` \| `other` | Yes | — | Feedback category: bug (something broken), feature (new capability request), question (how-to), other | | `message` | string | Yes | — | The feedback body text (min length: 1) | | `route` | string | Yes | — | The app route or context where the feedback originates (e.g. ‘/dashboard/campaigns’ or ‘mcp-agent’) (min length: 1) | *** # Workspace Management > Manage workspace settings, team members, and invitations. Manage workspace settings, team members, and invitations. ## Tools (8) [Section titled “Tools (8)”](#tools-8) ### get\_workspace [Section titled “get\_workspace”](#get_workspace) **Full name:** `campaignstack_get_workspace` workspace:read Read-only Idempotent Get workspace details including name, company info, industry, and settings. The workspaceId is returned by campaignstack\_whoami. #### Parameters [Section titled “Parameters”](#parameters) | Name | Type | Required | Default | Description | | ------------- | ------ | -------- | ------- | --------------- | | `workspaceId` | string | Yes | — | (min length: 1) | *** ### update\_workspace [Section titled “update\_workspace”](#update_workspace) **Full name:** `campaignstack_update_workspace` workspace:write Idempotent Update workspace settings. Only provided fields are updated. Use campaignstack\_get\_workspace to see current values. #### Parameters [Section titled “Parameters”](#parameters-1) | Name | Type | Required | Default | Description | | ------------------- | ------ | -------- | ------- | --------------- | | `workspaceId` | string | Yes | — | (min length: 1) | | `name` | string | No | — | (min length: 1) | | `companyName` | string | No | — | | | `companyWebsiteUrl` | string | No | — | | | `industry` | string | No | — | | *** ### list\_members [Section titled “list\_members”](#list_members) **Full name:** `campaignstack_list_members` workspace:read Read-only Idempotent List all members of a workspace with their roles (owner, admin, member), names, and emails. #### Parameters [Section titled “Parameters”](#parameters-2) | Name | Type | Required | Default | Description | | ------------- | ------ | -------- | ------- | --------------- | | `workspaceId` | string | Yes | — | (min length: 1) | *** ### update\_member\_role [Section titled “update\_member\_role”](#update_member_role) **Full name:** `campaignstack_update_member_role` workspace:write Idempotent Update a member’s role. Via MCP, roles can only be set to ‘member’ — granting admin or transferring ownership requires the workspace settings UI. Use campaignstack\_list\_members to find member IDs. #### Parameters [Section titled “Parameters”](#parameters-3) | Name | Type | Required | Default | Description | | ------------- | -------- | -------- | ------- | -------------------------------- | | `workspaceId` | string | Yes | — | (min length: 1) | | `memberId` | string | Yes | — | (min length: 1) | | `role` | `member` | Yes | — | Only “member” can be set via MCP | *** ### remove\_member [Section titled “remove\_member”](#remove_member) **Full name:** `campaignstack_remove_member` workspace:write Destructive Remove a member from the workspace. Cannot remove the workspace owner or admins (admin removal requires the workspace settings UI). This action is irreversible. #### Parameters [Section titled “Parameters”](#parameters-4) | Name | Type | Required | Default | Description | | ------------- | ------ | -------- | ------- | --------------- | | `workspaceId` | string | Yes | — | (min length: 1) | | `memberId` | string | Yes | — | (min length: 1) | *** ### list\_invitations [Section titled “list\_invitations”](#list_invitations) **Full name:** `campaignstack_list_invitations` workspace:read Read-only Idempotent List pending invitations for a workspace. Returns email, role, and invitation date. #### Parameters [Section titled “Parameters”](#parameters-5) | Name | Type | Required | Default | Description | | ------------- | ------ | -------- | ------- | --------------- | | `workspaceId` | string | Yes | — | (min length: 1) | *** ### invite\_member [Section titled “invite\_member”](#invite_member) **Full name:** `campaignstack_invite_member` workspace:write Invite a user to the workspace by email (role ‘member’ only — admin invitations require the workspace settings UI; the inviter is the workspace owner). An invitation email is sent automatically. The invitation expires after 7 days. Use campaignstack\_list\_invitations to check pending invitations. #### Parameters [Section titled “Parameters”](#parameters-6) | Name | Type | Required | Default | Description | | ------------- | -------- | -------- | ------- | ------------------------------------------------ | | `workspaceId` | string | Yes | — | (min length: 1) | | `email` | string | Yes | — | | | `role` | `member` | Yes | — | Only “member” invitations can be created via MCP | *** ### revoke\_invitation [Section titled “revoke\_invitation”](#revoke_invitation) **Full name:** `campaignstack_revoke_invitation` workspace:write Destructive Revoke a pending invitation. The invitation link becomes invalid immediately. #### Parameters [Section titled “Parameters”](#parameters-7) | Name | Type | Required | Default | Description | | -------------- | ------ | -------- | ------- | --------------- | | `workspaceId` | string | Yes | — | (min length: 1) | | `invitationId` | string | Yes | — | (min length: 1) | *** # Transports > How to connect to the hosted CampaignStack MCP server over HTTP. The MCP server is available over HTTP. ## HTTP [Section titled “HTTP”](#http) Streamable HTTP transport. * **URL:** `https://mcp.campaignstack.io/mcp` * **Auth:** Required — `Authorization: Bearer csu_...` (user key) or `cs_...` (workspace key) * **Protocol:** JSON-RPC 2.0 over HTTP, with SSE for streaming ### Session lifecycle [Section titled “Session lifecycle”](#session-lifecycle) 1. **Initialize** — `POST /mcp` with `initialize` method. Server returns a session ID in the `mcp-session-id` header. 2. **Tool calls** — `POST /mcp` with `tools/call` method, include session ID header. 3. **Streaming** — `GET /mcp` with session ID for SSE event stream. 4. **Close** — `DELETE /mcp` with session ID. ### Health check [Section titled “Health check”](#health-check) ```bash curl https://mcp.campaignstack.io/health ``` Returns server version, status, and the complete list of available tools (no authentication required). ## stdio [Section titled “stdio”](#stdio) Not available. The MCP server is hosted — connect via the HTTP transport above.