Sprootal Public API

Sprootal's REST API for managing contacts, automations, messages, and sender profiles across your LinkedIn outreach. Authenticate with API keys and subscribe to webhooks for real-time events.

Base URL: https://sprootal.com/api/v1. Authenticate with Authorization: Bearer lg_live_…. The machine-readable spec is at /api/v1/openapi.json.

Contacts

Contact management operations

POST /contacts/search — Search contacts

Advanced search with filters for tags, stages, and text query. Searches in firstName, lastName, title, linkedin, and email.

GET /contacts/{id}/messages — Get contact messages

Returns messages for a specific contact

GET /contacts/{id}/drafts — List drafts for a contact

Returns message drafts in the contact conversation with the given sender. Defaults to pending and edited drafts; pass ?status= to filter.

POST /contacts/{id}/drafts — Create a draft for a contact

Creates a reply draft in the contact conversation with the given sender, for human review in the Inbox. With autoSend=true (owner-created API keys only) the draft skips review and is placed in the API queue instead, answering 202 — it is NOT on the wire when the call returns; the sender lane paces it like any other queued message.

Messages

LinkedIn message operations

POST /contacts/{id}/messages — Send a message to a contact

Queues a LinkedIn message from the given sender. When the contact has no conversation with that sender yet, the send opens one. Returns 202: the message is paced by the sender lane, not sent inline. Delivery is reported through the message.sent webhook; failures are visible via GET /messages/queue?status=failed. text may contain {{variable}} placeholders, resolved against the contact at send time. Requires an API key created by a workspace owner or admin — an unattended send is theirs to authorize. If you want a human in the loop instead, create a draft.

Automations

Automation sequence operations

POST /automations — Create a campaign

Builds a campaign and saves it as a draft. Supply EITHER blocks (a linear chain you author) OR prompt (a plain-language brief the flow builder turns into a campaign) — never both. Creating never starts it: activate with PUT /automations/{id}/status when a human is ready. With blocks, branching types (condition, ab_test, wait_reply, check_connection) are not expressible in a linear chain and are rejected with a message pointing at the flow builder. With prompt, the builder MAY produce a branching flow — it is required to gate send_message behind check_connection — so a generated campaign reads back with structure.linear: false. Generation spends AI tokens, needs the aiSequenceBuilder feature and an API key linked to a user account, and answers synchronously (tens of seconds).

GET /automations — List automations

Returns a paginated list of automations

GET /automations/{id} — Get automation

Returns detailed information about a campaign including stats and, under structure, the linear block chain it was built from. A branching campaign returns structure.linear: false and structure.blocks: null — a partial chain would read as the whole campaign.

POST /automations/{id}/enroll — Enroll contacts

Enrolls contacts into an automation sequence

POST /automations/{id}/unenroll — Unenroll contacts

Removes contacts from an automation sequence

PUT /automations/{id}/status — Update automation status

Pause or resume an automation

Webhooks

Webhook endpoint management

POST /webhooks/{id}/test — Send a test delivery

Queues a signed test delivery to this endpoint through the normal dispatch path (same signature, SSRF check, retry policy and delivery log as a real event). The endpoint does not need to be subscribed to the event or enabled. Returns as soon as the delivery is queued; poll the deliveries endpoint for the result.

Projects

Project management (read-only)

GET /projects — List projects

Returns a paginated list of projects

Lists

Contact list management

GET /lists — List contact lists

Returns a paginated list of contact lists. Optionally filter by project.

POST /lists — Create contact list

Creates a new contact list and associates it with a project

Deals

Deal pipeline management

GET /deals — List deals

Returns a paginated list of deals, most recently updated first. Optionally filter by project and status.

POST /deals — Create deal

Creates a deal in a project pipeline stage. The stage must belong to the project, and the deal status is derived from the stage outcome.

GET /deals/summary — Get deal pipeline summary

Returns open / won / lost counts and values, the win rate, and a per-stage breakdown.

GET /deals/{id} — Get deal

PATCH /deals/{id} — Update deal

Applies an outcome action (lost / won / reopen) or edits deal fields. Returns the deal as it stands after the change.

DELETE /deals/{id} — Delete deal

Soft deletes a deal

Tasks

Task inbox, creation, and lifecycle actions (claim / snooze / complete / reassign / execute / review).

POST /tasks/webhook — Fire a webhook-source task rule

External systems POST here to fire rules whose triggerSource is "webhook". Enforces the team kill-switch and the rule's conditions.

Drafts

AI reply drafts a human reviews before anything reaches a real person.

GET /drafts/{draftId} — Get a draft

DELETE /drafts/{draftId} — Discard a draft

Discards a pending or edited draft.

POST /drafts/{draftId}/approve — Approve a draft and place it in the API queue

Places the draft text in the API queue for its sender account and returns 202 immediately. The message is NOT on the wire when this call returns: each sender drains one message at a time under the same pacing the automation scheduler applies — connected account, inside working hours, daily capacity remaining, at least two minutes between sends. Because of that this endpoint never answers 429 for pacing; you can approve a batch without retry logic. Delivery is reported through the message.sent webhook; failures are visible via GET /messages/queue?status=failed. Requires an API key created by a workspace owner or admin.

Queue

Every machine-initiated action shares one rail. A sender drains it one action at a time, at least two minutes apart, inside its working hours and within its own API-queue budget — so a message and a connection request never leave the same LinkedIn account at the same moment.

Nothing here sends inside the request. Each endpoint answers 202 with a queue position and an estimated send time. What actually happened is in GET /queue: the resolution field says sent, or names the reason the queue declined to spend the action.

What you can queue

  • Send a message — POST /contacts/{id}/messages · scope messages:write. Opens the conversation when there is none yet.
  • Send a draft a human approved — POST /drafts/{draftId}/approve · scope messages:write.
  • Send a connection request — POST /connection-requests · scope connections:write. An optional note, ≤300 characters.
  • Visit a profile — POST /profile-visits · scope engagements:write. Shows up in their “who viewed your profile”. A repeat visit the same day costs nothing.
  • Like a post — POST /post-likes · scope engagements:write. You name the post; this never picks one for you.

Address a contact by its contact_id. A connection request also accepts a linkedin_url instead, and creates the contact when the URL is new to the workspace.

curl -X POST https://sprootal.com/api/v1/connection-requests \
  -H "Authorization: Bearer $SPROOTAL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"sender_id":"<uuid>","linkedin_url":"https://www.linkedin.com/in/ada-lovelace","note":"Saw your GTM post."}'

Execution conditions

An action can carry the condition under which it should still happen when its turn comes — which may be minutes or days after you asked for it. Omit them and the action simply runs.

  • skip_if_replied on a message — stand down if the contact writes back after the message was queued. The item settles cancelled with resolution: already_replied.
  • withdraw_after_hours on a connection request — withdraw the invitation if it is still unaccepted this many hours later (1–720). Leave it out and the invitation stands.
  • skip_note_if_limit_reached on a connection request — LinkedIn meters note-bearing invitations under a smaller weekly quota; when it is exhausted, retry the same invitation without the note rather than failing.

A withdrawal is not a timer hidden inside the invitation: it joins the queue as its own action, scheduled forward, so it is paced, budgeted and visible exactly like everything else — and you can cancel it.

curl -X POST https://sprootal.com/api/v1/contacts/$CONTACT_ID/messages \
  -H "Authorization: Bearer $SPROOTAL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"sender_id":"<uuid>","text":"Following up on my note.","skip_if_replied":true}'

Reading the rail

GET /queue sees every action type and filters by status, action_type, sender_id and contact_id. GET /messages/queue is the message-only view kept for integrations that predate the others.

There is deliberately no send-failure webhook: the queue is the failure surface, so poll status=failed here. resolution also separates a real failure from a deliberate stand-down — already_connected, already_invited and already_replied mean the queue refused to spend an action it no longer believed in.

Budgets and pacing

Each sender account carries a daily API-queue budget per action type, kept separate from what campaigns may spend on the same account. An exhausted budget does not fail the action, it holds it until the account daily reset. Working hours, the two-minute gap between actions and the invite brake apply to everything on the rail.

POST /connection-requests — Queue a LinkedIn connection request

Queues a connection request from the given sender and returns 202 — it is NOT on the wire when this call returns. It joins the same per-sender lane as messages and is paced by the same gate (connected account, inside working hours, invite capacity remaining, at least two minutes since that sender last acted), so a queued invitation and a queued message never leave one account at the same moment. Address the recipient with EITHER contact_id OR linkedin_url; an unknown URL creates the contact. Before the invitation goes out the queue re-checks that the recipient is not already a connection and has no invitation still open from this sender within the last 21 days — either way the item settles as cancelled with a failure_code rather than spending one of the scarcest quotas LinkedIn meters. Poll GET /queue for the outcome. Requires an API key created by a workspace owner or admin.

POST /profile-visits — Queue a LinkedIn profile visit

Queues a profile visit from the given sender and returns 202 — it is NOT performed when this call returns. It shares the per-sender lane with messages and connection requests and is paced by the same gate, so one account never performs two actions at once. A repeat visit to the same contact on the same day does not spend a second visit from the daily quota: the visit is recorded, and the second call reuses that record rather than charging again. Poll GET /queue for the outcome. Requires an API key created by a workspace owner or admin.

POST /post-likes — Queue a LIKE on a LinkedIn post

Queues a LIKE on ONE specific post and returns 202 — it is NOT performed when this call returns. It shares the per-sender lane with messages, connection requests and profile visits, and is paced by the same gate. You name the post; this endpoint never picks one for you. Poll GET /queue for the outcome. Requires an API key created by a workspace owner or admin.

MCP Server

Sprootal CRM speaks the Model Context Protocol over Streamable HTTP, so an AI client such as Claude, Claude Code or Cursor can work inside your Sprootal workspace directly. It exposes the same data as the REST API above, governed by the same API-key scopes.

Endpoint POST https://sprootal.com/api/mcp · Server version 1.0.0 · 91 tools across 15 groups

Connecting

Claude, Cursor and other OAuth clients

Add https://sprootal.com/api/mcp as a custom connector. The server implements OAuth 2.1 with dynamic client registration and PKCE, so there is nothing to pre-provision: the client discovers its endpoints from https://sprootal.com/.well-known/oauth-protected-resource and takes you through a consent screen where you choose which scope groups to grant.

Headless clients (API key)

Clients that cannot run an OAuth flow send a Sprootal API key as a bearer token instead. Create one under Settings → API keys; lg_test_* keys behave identically to lg_live_*.

{
  "mcpServers": {
    "sprootal": {
      "type": "http",
      "url": "https://sprootal.com/api/mcp",
      "headers": {
        "Authorization": "Bearer lg_live_your_key_here"
      }
    }
  }
}

Raw JSON-RPC

Every call is JSON-RPC 2.0. Open a session with initialize, then send the returned Mcp-Session-Id header back on each subsequent request.

curl -X POST https://sprootal.com/api/mcp \
  -H 'Authorization: Bearer lg_live_your_key_here' \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

Sessions, transport and limits

  • POST https://sprootal.com/api/mcp carries JSON-RPC requests, GET opens the server-to-client SSE stream, DELETE terminates a session.
  • The Mcp-Session-Id response header identifies the session. A session expires after 30 minutes idle or 4 hours total, whichever comes first.
  • Up to 5 concurrent sessions per API key; request bodies are capped at 256 KB.
  • Rate limits are shared with the REST API and reported on every response through X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset. A 401 carries a WWW-Authenticate header pointing at the resource metadata document.
  • Revoking or deleting an API key immediately closes every session opened with it.

Scopes

A tool call returns permission_denied when the key or token lacks the scope it needs. OAuth consent is granted in groups:

Consent groupScopes
Read your contacts and companiescontacts:read, companies:read, lists:read, tags:read
Update contacts and companiescontacts:write, companies:write, lists:write, tags:write
Read conversations and draftsmessages:read
Prepare message draftsmessages:write
Send LinkedIn connection requestsconnections:write
Visit profiles and like postsengagements:write
Read and manage campaignsautomations:read, automations:write
Read pipeline and reportsdeals:read, analytics:read, projects:read, sender_profiles:read
Update pipeline and projectsdeals:write, projects:write, sender_profiles:write
Read and manage taskstasks:read, tasks:write

Resources and prompts

Besides tools the server exposes two read-only resources — crm://team/summary (contact, campaign and project counts) and crm://schema/projects (projects with their pipeline stages and tags) — plus the prompts contact_summary, campaign_report and enrich_contact.

Tools

Contacts

Create, search, update and delete contacts, and manage their list memberships.

Scopes: contacts:read, contacts:write, lists:read, lists:write

create_contact

Create a contact, or update the one that already matches. Identity is the LinkedIn slug or URN. Calling it twice with the same person updates rather than duplicating, and restores the contact if it had been deleted. A projectId or a listId is required; for a LinkedIn connection request, pass the LinkedIn URL or the ACo… attendeeProviderId.

ArgumentTypeRequiredNotes
firstNamestringyes1–100 chars
lastNamestringyes1–100 chars
titlestring–max 200 chars
linkedinstring–max 255 chars
attendeeProviderIdstring–max 255 chars
projectIduuid–From list_projects
listIduuid–From list_lists
customFieldsobject–—
emailemail–max 255 chars
pipelineStagestring–1–255 chars
updateIfExistsboolean–default true
moveToListboolean–default true
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "create_contact",
    "arguments": {
      "firstName": "<string>",
      "lastName": "<string>"
    }
  }
}

delete_contact

Remove a contact. A soft delete: the contact stops appearing in every read, and support can restore it.

ArgumentTypeRequiredNotes
idstringyesFrom search_contacts · 1–36 chars
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "delete_contact",
    "arguments": {
      "id": "<string>"
    }
  }
}

get_contact

Get one contact, with its profile, engagement and conversations

ArgumentTypeRequiredNotes
idstringyesFrom search_contacts · 1–36 chars
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "get_contact",
    "arguments": {
      "id": "<string>"
    }
  }
}

search_contacts

List contacts. Scoped to a project with project_id, which applies that project visibility rules. q matches name, LinkedIn slug and provider id.

ArgumentTypeRequiredNotes
project_iduuid–From list_projects
qstring–max 200 chars
tag_idsuuid[]–Tag IDs, from list_tags
stage_idsuuid[]–Pipeline stage IDs, from list_pipeline_stages
page_sizestring–Items per page as a string, "1" to "100" (default 20); "0" returns only counts.total
cursorstring–pagination.next_cursor from the previous page; omit for the first page
sortstring–field or field:asc
filterstring–JSON object {field: {operator: value}}; a bare value means eq. Fields: first_name (string: eq, ne, in, nin, is_null, contains); last_name (string: eq, ne, in, nin, is_null, contains); title (string: eq, ne, in, nin, is_null, contains); project_id (string: eq, ne, in, nin, is_null, contains); created_at (date: eq, ne, gt, gte, lt, lte, is_null); updated_at (date: eq, ne, gt, gte, lt, lte, is_null). Example: {"first_name":{"contains":"..."}}
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "search_contacts",
    "arguments": {}
  }
}

update_contact

Change a contact details

ArgumentTypeRequiredNotes
idstringyesFrom search_contacts · 1–36 chars
firstNamestring–1–100 chars
lastNamestring–1–100 chars
titlestring–—
linkedinstring–—
projectIduuid–From list_projects
customFieldsobject–—
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "update_contact",
    "arguments": {
      "id": "<string>"
    }
  }
}

Messages

Read LinkedIn conversations and message history, inspect messaging stats, and queue a message or a connection request to a contact.

Scopes: messages:read, messages:write, connections:write, engagements:write

cancel_message_queue_item

Cancel a queued message before it sends

ArgumentTypeRequiredNotes
itemIdstringyesFrom search_message_queue · 1–64 chars
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "cancel_message_queue_item",
    "arguments": {
      "itemId": "<string>"
    }
  }
}

get_message

Get one message

ArgumentTypeRequiredNotes
idstringyesFrom search_messages · 1–64 chars
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "get_message",
    "arguments": {
      "id": "<string>"
    }
  }
}

like_post

Queue a LIKE on one specific LinkedIn post. It joins the same paced API queue as messages and invitations, so it is not performed instantly. Name the post you mean — this never picks a post for you.

ArgumentTypeRequiredNotes
contact_iduuidyesWhose post it is, so the like is attributable (search_contacts)
sender_iduuidyesWhich sender profile does the liking (search_senders)
post_urlstringyesA LinkedIn post URL (linkedin.com/posts/... or /feed/update/...) or a bare urn:li:activity: id · min 1 chars
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "like_post",
    "arguments": {
      "contact_id": "<uuid>",
      "sender_id": "<uuid>",
      "post_url": "<string>"
    }
  }
}

search_message_queue

List queued outbound actions. Covers every queued action type, not just messages. Filter with {"action_type":{"eq":"send_connection"}}.

ArgumentTypeRequiredNotes
page_sizestring–Items per page as a string, "1" to "100" (default 20); "0" returns only counts.total
cursorstring–pagination.next_cursor from the previous page; omit for the first page
sortstring–field or field:asc
filterstring–JSON object {field: {operator: value}}; a bare value means eq. Fields: status (enum queued
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "search_message_queue",
    "arguments": {}
  }
}

search_messages

List messages across the team. Narrow with contact_id, sender_profile_id or type. Newest first, cursor-paged.

ArgumentTypeRequiredNotes
contact_iduuid–From search_contacts
sender_profile_iduuid–From search_senders
typeinbox | outbox–—
page_sizestring–Items per page as a string, "1" to "100" (default 20); "0" returns only counts.total
cursorstring–pagination.next_cursor from the previous page; omit for the first page
sortstring–field or field:asc
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "search_messages",
    "arguments": {}
  }
}

send_connection_request

Queue a LinkedIn connection request to a contact. It joins the same paced API queue as messages, so it is not sent instantly — the response carries the queue position and an estimated send time. Connection requests are the scarcest LinkedIn quota there is; the queue refuses a duplicate to someone already invited or already connected rather than burning one. Requires an API key created by a workspace owner or admin.

ArgumentTypeRequiredNotes
contact_iduuidyesThe contact id, from search_contacts
sender_iduuidyesWhich sender profile the invitation comes from (search_senders)
notestring–An optional note, at most 300 characters. LinkedIn meters note-bearing invitations under a smaller weekly quota than note-free ones. Personalise with {{firstName}}, {{lastName}}, {{fullName}}, {{title}}, {{company}}, {{email}}, {{linkedin}}, {{phoneNumber}}, {{location}}, {{headline}}, {{sender.firstName}}, {{sender.lastName}}, {{sender.name}}, {{sender.title}}, {{sender.company}}, {{sender.fullName}}, or a project custom field as {{custom.<field>}}. Add a fallback for an empty value with {{firstName
skip_note_if_limit_reachedboolean–When the note quota is exhausted, retry the same invitation without the note instead of failing. Off by default — leave it off when the note is the point.
withdraw_after_hoursinteger–Withdraw the invitation if it is still unaccepted this many hours after it goes out. Left unset the invitation simply stands. The withdrawal joins the queue as its own action, so it is paced and visible like any other. · 1–720
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "send_connection_request",
    "arguments": {
      "contact_id": "<uuid>",
      "sender_id": "<uuid>"
    }
  }
}

send_message

Queue a LinkedIn message to a contact. The message enters the API queue and is paced against every other machine send on that sender, so it is not delivered instantly — the response carries the queue position and an estimated send time. Requires an API key created by a workspace owner or admin; otherwise use create_draft and have a human approve it.

ArgumentTypeRequiredNotes
contact_iduuidyesThe contact id, from search_contacts
sender_iduuidyesWhich sender profile the message comes from (search_senders)
textstringyesThe message body. Personalise with {{firstName}}, {{lastName}}, {{fullName}}, {{title}}, {{company}}, {{email}}, {{linkedin}}, {{phoneNumber}}, {{location}}, {{headline}}, {{sender.firstName}}, {{sender.lastName}}, {{sender.name}}, {{sender.title}}, {{sender.company}}, {{sender.fullName}}, or a project custom field as {{custom.<field>}}. Add a fallback for an empty value with {{firstName
skip_if_repliedboolean–Stand down if this contact writes back before the message leaves the queue. Off by default; turn it on for a follow-up that a reply would make wrong.
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "send_message",
    "arguments": {
      "contact_id": "<uuid>",
      "sender_id": "<uuid>",
      "text": "<string>"
    }
  }
}

update_message_queue_item

Edit a queued message, or move it in the queue. Only works while the message is still waiting. Once it is sending or sent the call is a conflict.

ArgumentTypeRequiredNotes
itemIdstringyesFrom search_message_queue · 1–64 chars
textstring–1–8000 chars
movetop | bottom–—
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "update_message_queue_item",
    "arguments": {
      "itemId": "<string>"
    }
  }
}

visit_profile

Queue a LinkedIn profile visit, which shows up in the recipient’s “who viewed your profile” list. It joins the same paced API queue as messages and invitations, so it is not performed instantly. A repeat visit to the same contact on the same day does not spend a second visit from the daily quota.

ArgumentTypeRequiredNotes
contact_iduuidyesThe contact id, from search_contacts
sender_iduuidyesWhich sender profile does the visiting (search_senders)
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "visit_profile",
    "arguments": {
      "contact_id": "<uuid>",
      "sender_id": "<uuid>"
    }
  }
}

withdraw_connection_request

Queue the withdrawal of a connection request this sender already sent to a contact and that is still unaccepted. It joins the same paced API queue as invitations, so it is not performed instantly; if the invitation turns out to be accepted or already gone by then, the queue item reports that instead of withdrawing anything. An invitation still waiting in the queue is not out yet: cancel it with cancel_message_queue_item instead. Requires an API key created by a workspace owner or admin.

ArgumentTypeRequiredNotes
contact_iduuidyesThe contact who holds the invitation, from search_contacts
sender_iduuidyesWhich sender profile sent the invitation (search_senders)
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "withdraw_connection_request",
    "arguments": {
      "contact_id": "<uuid>",
      "sender_id": "<uuid>"
    }
  }
}

Drafts

Create, list and discard AI reply drafts for a conversation.

Scopes: messages:read, messages:write

create_draft

Write a reply draft into a conversation for a human to review in the Inbox. This does NOT send anything — a person approves it. Use search_contacts to find the contact and search_senders to pick the sender.

ArgumentTypeRequiredNotes
contact_iduuidyesThe contact id, from search_contacts
sender_iduuidyesWhich sender profile the reply comes from (search_senders)
textstringyesThe proposed reply · 1–8000 chars
agent_namestring–Who wrote it, shown on the draft card · max 100 chars
playbook_labelstring–Template name this reply follows; a matching label lets the human regenerate against that template · max 100 chars
confidencenumber–0 to 1, shown to the reviewer · 0–1
metaobject–Free-form context kept with the draft
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "create_draft",
    "arguments": {
      "contact_id": "<uuid>",
      "sender_id": "<uuid>",
      "text": "<string>"
    }
  }
}

discard_draft

Withdraw a draft you created so it stops competing for the reviewer attention. Fails if a human already sent or discarded it.

ArgumentTypeRequiredNotes
draft_iduuidyesDraft ID, from list_drafts
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "discard_draft",
    "arguments": {
      "draft_id": "<uuid>"
    }
  }
}

list_drafts

List reply drafts in a conversation, including ones a human already sent or discarded

ArgumentTypeRequiredNotes
contact_iduuidyesThe contact id, from search_contacts
sender_iduuidyesWhich sender profile the conversation is with (search_senders)
statuspending | edited | queued | sent | declined | obsolete[]–Defaults to drafts still awaiting review
limitnumber–Items per page · 1–100 · default 50
offsetnumber–Items to skip. While pagination.hasMore, pass offset + limit for the next page · ≥ 0 · default 0
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "list_drafts",
    "arguments": {
      "contact_id": "<uuid>",
      "sender_id": "<uuid>"
    }
  }
}

Campaigns

Manage outreach campaigns: create, enrol and unenrol contacts, change status, read stats.

Scopes: automations:read, automations:write

create_campaign

Create a new outreach campaign from either a free-text brief (prompt) or an explicit step chain (blocks) — exactly one of the two, never both. prompt runs the same campaign builder as the app and writes the copy from the project context. blocks is a plain linear list of the steps you want, and the same safety steps the builder uses are added around it: a connection check first, so contacts who are already connected skip the invite; after an invite, a wait for acceptance and a withdrawal if it is not accepted; after a message, a wait for a reply. The response lists what was added in addedSteps. Describe the audience and goal in description. The campaign is always created as a DRAFT and sends nothing: a person must activate it, and senders are chosen in the app, so do not tell the user the campaign has been launched.

ArgumentTypeRequiredNotes
namestringyesCampaign name · 1–200 chars
descriptionstringyesWho the campaign targets and what it is for, in at least a sentence. Shown in the app and used as the brief when no prompt is given. · 20–1000 chars
project_iduuidyesProject the campaign belongs to, from list_projects
blocksobject[]–Explicit linear chain of campaign steps; connection checks, acceptance waits and reply waits are added around it. Mutually exclusive with prompt. Personalise with {{firstName}}, {{lastName}}, {{fullName}}, {{title}}, {{company}}, {{email}}, {{linkedin}}, {{phoneNumber}}, {{location}}, {{headline}}, {{sender.firstName}}, {{sender.lastName}}, {{sender.name}}, {{sender.title}}, {{sender.company}}, {{sender.fullName}}, or a project custom field as {{custom.<field>}}. Add a fallback for an empty value with {{firstName
promptstring–Free-text brief the campaign flow is generated from. Spends AI tokens. Mutually exclusive with blocks. · 1–2000 chars
service_idstring–Service whose context steers generation, for the prompt path · min 1 chars
scenario_idstring–Scenario whose context steers generation, for the prompt path · min 1 chars
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "create_campaign",
    "arguments": {
      "name": "<string>",
      "description": "<string>",
      "project_id": "<uuid>"
    }
  }
}

delete_campaign

Delete a DRAFT campaign. The deletion is soft: the campaign disappears from lists and can be restored from the app. A campaign that has been activated cannot be deleted here, because contacts are moving through it; pause it with update_campaign_status instead.

ArgumentTypeRequiredNotes
campaign_iduuidyesCampaign ID, from list_campaigns
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "delete_campaign",
    "arguments": {
      "campaign_id": "<uuid>"
    }
  }
}

enroll_contacts

Enroll up to 100 contacts into a campaign. In an active campaign its steps start reaching them, so confirm with the user first. Contacts already enrolled are skipped; the result counts enrolled, alreadyEnrolled and notFound.

ArgumentTypeRequiredNotes
campaign_iduuidyesCampaign ID, from list_campaigns
contact_idsuuid[]yesContact IDs to enroll, from search_contacts
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "enroll_contacts",
    "arguments": {
      "campaign_id": "<uuid>",
      "contact_ids": [
        "<uuid>"
      ]
    }
  }
}

get_campaign

Get campaign details including stats, enrolled contacts summary, and the campaign structure: a linear block chain when it has one, otherwise every step with the step each outcome leads to

ArgumentTypeRequiredNotes
campaign_iduuidyesCampaign ID, from list_campaigns
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "get_campaign",
    "arguments": {
      "campaign_id": "<uuid>"
    }
  }
}

list_campaigns

List campaigns with optional status and project filters

ArgumentTypeRequiredNotes
statusstring–Filter by status (draft, active, paused, completed)
project_iduuid–Filter by project ID, from list_projects
limitnumber–Items per page · 1–100 · default 20
offsetnumber–Items to skip. While pagination.hasMore, pass offset + limit for the next page · ≥ 0 · default 0
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "list_campaigns",
    "arguments": {}
  }
}

unenroll_contacts

Remove contacts from a campaign sequence

ArgumentTypeRequiredNotes
campaign_iduuidyesCampaign ID, from list_campaigns
contact_idsuuid[]yesContact IDs to unenroll, from search_contacts
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "unenroll_contacts",
    "arguments": {
      "campaign_id": "<uuid>",
      "contact_ids": [
        "<uuid>"
      ]
    }
  }
}

update_campaign

Edit a DRAFT campaign: rename it, change its description, or replace its steps with new blocks or a new prompt (one of the two, never both). Replacing the steps rebuilds the whole flow the same way create_campaign does. A campaign that has been activated cannot be edited here, because contacts are already moving through its steps; edit it in the app.

ArgumentTypeRequiredNotes
campaign_iduuidyesCampaign ID, from list_campaigns
namestring–New name · 1–200 chars
descriptionstring–New description of who the campaign targets and why · 20–1000 chars
blocksobject[]–New step chain, replacing every current step. Same rules as create_campaign. Mutually exclusive with prompt.
promptstring–New brief to regenerate every step from. Mutually exclusive with blocks. · 1–2000 chars
service_idstring–Service whose context steers generation, for prompt · min 1 chars
scenario_idstring–Scenario whose context steers generation, for prompt · min 1 chars
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "update_campaign",
    "arguments": {
      "campaign_id": "<uuid>"
    }
  }
}

update_campaign_status

Activate or pause a campaign. Activating starts real LinkedIn sends to every enrolled contact, so do it only on the user explicit instruction. Pausing stops new sends; a completed campaign cannot change status.

ArgumentTypeRequiredNotes
campaign_iduuidyesCampaign ID, from list_campaigns
statusactive | pausedyesNew status
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "update_campaign_status",
    "arguments": {
      "campaign_id": "<uuid>",
      "status": "active"
    }
  }
}

Projects

Read projects and their pipeline stages, and list or create project custom fields.

Scopes: projects:read, projects:write

add_contacts_to_list

Add contacts to a list

ArgumentTypeRequiredNotes
list_iduuidyesList ID, from list_lists
contact_idsuuid[]yesContact IDs to add, from search_contacts
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "add_contacts_to_list",
    "arguments": {
      "list_id": "<uuid>",
      "contact_ids": [
        "<uuid>"
      ]
    }
  }
}

create_custom_field

Create a custom field in a project. fieldKey defaults to fieldName in snake_case and must be unique in the project. select and multi_select need options.choices; currency needs currencyCode. Without order the field is appended last.

ArgumentTypeRequiredNotes
iduuidyesProject id, from list_projects
fieldNamestringyes1–100 chars
fieldKeystring–1–100 chars
fieldTypetext | number | date | select | multi_select | boolean | url | email | phone | currency | percentyes—
descriptionstring–max 500 chars
isRequiredboolean–default false
defaultValuestring–max 1000 chars
optionsobject–—
validationobject–—
currencyCodestring–3–3 chars
orderinteger–0–9007199254740991
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "create_custom_field",
    "arguments": {
      "id": "<uuid>",
      "fieldName": "<string>",
      "fieldType": "text"
    }
  }
}

create_list

Create a new contact list

ArgumentTypeRequiredNotes
namestringyesList name · 1–100 chars
project_iduuid–Associate with a project, from list_projects
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "create_list",
    "arguments": {
      "name": "<string>"
    }
  }
}

list_lists

List contact lists, optionally filtered by project

ArgumentTypeRequiredNotes
project_iduuid–Filter by project ID, from list_projects
limitnumber–Items per page · 1–100 · default 20
offsetnumber–Items to skip. While pagination.hasMore, pass offset + limit for the next page · ≥ 0 · default 0
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "list_lists",
    "arguments": {}
  }
}

list_projects

List all projects

ArgumentTypeRequiredNotes
limitnumber–Items per page · 1–100 · default 20
offsetnumber–Items to skip. While pagination.hasMore, pass offset + limit for the next page · ≥ 0 · default 0
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "list_projects",
    "arguments": {}
  }
}

remove_contacts_from_list

Remove contacts from a list

ArgumentTypeRequiredNotes
list_iduuidyesList ID, from list_lists
contact_idsuuid[]yesContact IDs to remove, from search_contacts
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "remove_contacts_from_list",
    "arguments": {
      "list_id": "<uuid>",
      "contact_ids": [
        "<uuid>"
      ]
    }
  }
}

search_custom_fields

List the active custom fields of a project. Search by name with filter {"field_name":{"contains":"..."}} (case-insensitive). fieldKey is the key to use in a contact customFields object for this project.

ArgumentTypeRequiredNotes
iduuidyesProject id, from list_projects
page_sizestring–Items per page as a string, "1" to "100" (default 50); "0" returns only counts.total
cursorstring–pagination.next_cursor from the previous page; omit for the first page
sortstring–field or field:asc
filterstring–JSON object {field: {operator: value}}; a bare value means eq. Fields: field_name (string: eq, ne, in, nin, is_null, contains); field_key (string: eq, ne, in, nin, is_null, contains); field_type (enum text
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "search_custom_fields",
    "arguments": {
      "id": "<uuid>"
    }
  }
}

Sender Profiles

Read connected LinkedIn sender accounts and their limits.

Scopes: sender_profiles:read

check_connections

Check which team senders have each LinkedIn person as a 1st-degree connection. Takes up to 200 LinkedIn profile URLs, slugs or member URNs and returns, in the same order, whether any sender of the team is connected with that person and which senders. It reads the synced connection lists only: it never visits a profile and uses no sender limits. A new connection shows up after the sender connection sync, usually within minutes and at most six hours.

ArgumentTypeRequiredNotes
identifiersstring[]yesLinkedIn profile URLs, slugs or member URNs
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "check_connections",
    "arguments": {
      "identifiers": [
        "<string>"
      ]
    }
  }
}

get_sender

Get one sender, with its full limits, sync state and mailbox counts

ArgumentTypeRequiredNotes
idstringyesFrom search_senders · 1–64 chars
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "get_sender",
    "arguments": {
      "id": "<string>"
    }
  }
}

get_sender_cooldown

Check whether a sender is cooling down. Ask before executing: a sender inside its cooldown refuses the send, and this says how long to wait.

ArgumentTypeRequiredNotes
idstringyesFrom search_senders · 1–64 chars
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "get_sender_cooldown",
    "arguments": {
      "id": "<string>"
    }
  }
}

get_sender_daily_limit

Check a sender daily allowance for one action type

ArgumentTypeRequiredNotes
idstringyesFrom search_senders · 1–64 chars
actionTypesend_connection | send_message | send_inmail | visit_profile | like_post | comment_post | withdraw_connectionyes—
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "get_sender_daily_limit",
    "arguments": {
      "id": "<string>",
      "actionType": "send_connection"
    }
  }
}

search_senders

List connected LinkedIn sender accounts. Filter with {"status":{"eq":"connected"}} to find the senders that can actually send right now, or {"name":{"contains":"anna"}} to resolve a sender the user named.

ArgumentTypeRequiredNotes
page_sizestring–Items per page as a string, "1" to "100" (default 20); "0" returns only counts.total
cursorstring–pagination.next_cursor from the previous page; omit for the first page
sortstring–field or field:asc
filterstring–JSON object {field: {operator: value}}; a bare value means eq. Fields: status (string: eq, ne, in, nin, is_null, contains); name (string: eq, ne, in, nin, is_null, contains); created_at (date: eq, ne, gt, gte, lt, lte, is_null). Example: {"name":{"contains":"..."}}
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "search_senders",
    "arguments": {}
  }
}

Webhooks

Manage webhook endpoints and inspect delivery attempts for your team.

Scopes: webhooks:manage

act_on_webhook

Rotate an endpoint signing secret, or re-enable it. Rotation keeps the previous secret valid for a grace window so deliveries in flight still verify. Re-enabling also clears the failure counter.

ArgumentTypeRequiredNotes
idstringyesFrom search_webhooks · 1–64 chars
actionrotate_secret | enableyes—
grace_secondsinteger–0–604800
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "act_on_webhook",
    "arguments": {
      "id": "<string>",
      "action": "rotate_secret"
    }
  }
}

create_webhook

Register a webhook endpoint. The signing secret is returned once, here. Store it: it is never shown again, and the only way to get a new one is to rotate.

ArgumentTypeRequiredNotes
namestringyes1–100 chars
urluriyes—
eventslead.created | lead.updated | lead.campaign_completed | conversation.new_message | message.sent | connection.accepted | draft.created | draft.approved | draft.discarded | draft.superseded | deal.created | deal.updated | deal.stage_changed | deal.won | deal.lost | deal.deleted | webhook.disabled[]yes—
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "create_webhook",
    "arguments": {
      "name": "<string>",
      "url": "<uri>",
      "events": [
        "lead.created"
      ]
    }
  }
}

delete_webhook

Permanently delete an endpoint and its delivery history. Irreversible, and the signing secret goes with it. To stop deliveries without losing the endpoint, set enabled=false instead. Destructive: call without confirm first to preview.

ArgumentTypeRequiredNotes
idstringyesFrom search_webhooks · 1–64 chars
confirmstring–Token from this tool preview. Omit to preview; pass it back to execute.
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "delete_webhook",
    "arguments": {
      "id": "<string>"
    }
  }
}

get_webhook

Get one webhook endpoint, with its delivery health

ArgumentTypeRequiredNotes
idstringyesFrom search_webhooks · 1–64 chars
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "get_webhook",
    "arguments": {
      "id": "<string>"
    }
  }
}

get_webhook_delivery

Get one delivery attempt, with its payload and response

ArgumentTypeRequiredNotes
idstringyesFrom search_webhooks · 1–64 chars
deliveryIdstringyesFrom search_webhook_deliveries · 1–64 chars
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "get_webhook_delivery",
    "arguments": {
      "id": "<string>",
      "deliveryId": "<string>"
    }
  }
}

replay_webhook_delivery

Send a past delivery again, to the endpoint it was for

ArgumentTypeRequiredNotes
idstringyesFrom search_webhooks · 1–64 chars
deliveryIdstringyesFrom search_webhook_deliveries · 1–64 chars
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "replay_webhook_delivery",
    "arguments": {
      "id": "<string>",
      "deliveryId": "<string>"
    }
  }
}

search_webhook_deliveries

List an endpoint delivery attempts. The first thing to look at when a webhook stops arriving. The filter {"success":{"eq":false}} shows only the failures, and page_size=0 counts them without fetching any.

ArgumentTypeRequiredNotes
idstringyesFrom search_webhooks · 1–64 chars
page_sizestring–Items per page as a string, "1" to "200" (default 50); "0" returns only counts.total
cursorstring–pagination.next_cursor from the previous page; omit for the first page
sortstring–field or field:asc
filterstring–JSON object {field: {operator: value}}; a bare value means eq. Fields: event (enum lead.created
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "search_webhook_deliveries",
    "arguments": {
      "id": "<string>"
    }
  }
}

search_webhooks

List webhook endpoints

ArgumentTypeRequiredNotes
page_sizestring–Items per page as a string, "1" to "200" (default 50); "0" returns only counts.total
cursorstring–pagination.next_cursor from the previous page; omit for the first page
sortstring–field or field:asc
filterstring–JSON object {field: {operator: value}}; a bare value means eq. Fields: name (string: eq, ne, in, nin, is_null, contains); enabled (boolean: eq, is_null); created_at (date: eq, ne, gt, gte, lt, lte, is_null). Example: {"name":{"contains":"..."}}
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "search_webhooks",
    "arguments": {}
  }
}

update_webhook

Change an endpoint name, URL, events or enabled state

ArgumentTypeRequiredNotes
idstringyesFrom search_webhooks · 1–64 chars
namestring–1–100 chars
urluri–—
eventslead.created | lead.updated | lead.campaign_completed | conversation.new_message | message.sent | connection.accepted | draft.created | draft.approved | draft.discarded | draft.superseded | deal.created | deal.updated | deal.stage_changed | deal.won | deal.lost | deal.deleted | webhook.disabled[]–—
enabledboolean–—
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "update_webhook",
    "arguments": {
      "id": "<string>"
    }
  }
}

Tags

Create project tags and attach or detach them from contacts.

Scopes: tags:read, tags:write

add_tag_to_contact

Add a tag to a contact in a project

ArgumentTypeRequiredNotes
project_iduuidyesProject ID, from list_projects
contact_iduuidyesContact ID, from search_contacts
tag_iduuidyesTag ID, from list_tags
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "add_tag_to_contact",
    "arguments": {
      "project_id": "<uuid>",
      "contact_id": "<uuid>",
      "tag_id": "<uuid>"
    }
  }
}

create_tag

Create a new tag in a project

ArgumentTypeRequiredNotes
project_iduuidyesProject ID, from list_projects
namestringyesTag name · 1–100 chars
text_colorstring–Hex text color · max 7 chars
background_colorstring–Hex background color · max 7 chars
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "create_tag",
    "arguments": {
      "project_id": "<uuid>",
      "name": "<string>"
    }
  }
}

list_tags

List tags for a project

ArgumentTypeRequiredNotes
project_iduuidyesProject ID, from list_projects
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "list_tags",
    "arguments": {
      "project_id": "<uuid>"
    }
  }
}

remove_tag_from_contact

Remove a tag from a contact in a project

ArgumentTypeRequiredNotes
project_iduuidyesProject ID, from list_projects
contact_iduuidyesContact ID, from search_contacts
tag_iduuidyesTag ID, from list_tags
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "remove_tag_from_contact",
    "arguments": {
      "project_id": "<uuid>",
      "contact_id": "<uuid>",
      "tag_id": "<uuid>"
    }
  }
}

Companies

Read, create and update company records.

Scopes: companies:read, companies:write

create_company

Create a company, or return the existing match. Resolves against domain, LinkedIn id and name before inserting, so calling it twice with the same company yields one record rather than a duplicate.

ArgumentTypeRequiredNotes
namestringyes1–255 chars
websitestring–max 255 chars
domainstring–max 255 chars
linkedinCompanyIdstring–max 64 chars
linkedinstring–max 255 chars
industrystring–max 255 chars
sizestring–max 50 chars
marketstring–max 50 chars
phoneNumberstring–max 100 chars
emailemail–max 255 chars
addressstring–max 255 chars
descriptionstring–—
typestring–max 50 chars
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "create_company",
    "arguments": {
      "name": "<string>"
    }
  }
}

get_company

Get one company, with the contacts linked to it. The contacts expansion carries each person role and dates at the company, so a caller does not have to fetch and join them itself.

ArgumentTypeRequiredNotes
iduuidyesFrom search_companies
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "get_company",
    "arguments": {
      "id": "<uuid>"
    }
  }
}

search_companies

List companies

ArgumentTypeRequiredNotes
page_sizestring–Items per page as a string, "1" to "100" (default 20); "0" returns only counts.total
cursorstring–pagination.next_cursor from the previous page; omit for the first page
sortstring–field or field:asc
filterstring–JSON object {field: {operator: value}}; a bare value means eq. Fields: name (string: eq, ne, in, nin, is_null, contains); industry (string: eq, ne, in, nin, is_null, contains); size (string: eq, ne, in, nin, is_null, contains); status (string: eq, ne, in, nin, is_null, contains); created_at (date: eq, ne, gt, gte, lt, lte, is_null). Example: {"name":{"contains":"..."}}
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "search_companies",
    "arguments": {}
  }
}

update_company

Change a company details. Renaming a company also updates the company shown on every contact linked to it.

ArgumentTypeRequiredNotes
iduuidyesFrom search_companies
namestring–1–255 chars
domainstring–—
websitestring–—
linkedinstring–—
industrystring–—
sizestring–—
marketstring–—
phoneNumberstring–—
emailemail–—
addressstring–—
descriptionstring–—
typestring–—
statusactive | inactive | archived–—
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "update_company",
    "arguments": {
      "id": "<uuid>"
    }
  }
}

Deals

Read the deal pipeline, create deals and move them between stages.

Scopes: deals:read, deals:write

create_deal

Create a new deal for a contact in a project pipeline stage

ArgumentTypeRequiredNotes
project_idstringyesProject ID, from list_projects
contact_idstringyesPrimary contact ID, from search_contacts
contact_idsstring[]–All contact IDs on the deal (first = primary), from search_contacts
stage_idstringyesPipeline stage ID, from list_pipeline_stages
company_idstring–Associated company ID, from search_companies
titlestring–max 255 chars
amount_minorinteger–Amount in minor units (e.g. cents) · 0–9007199254740991
currencystring–ISO 4217 currency code · 3–3 chars
priorityhigh | normal | low–—
owner_idstring–Owning team member ID
expected_close_datestring–Expected close date (YYYY-MM-DD)
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "create_deal",
    "arguments": {
      "project_id": "<string>",
      "contact_id": "<string>",
      "stage_id": "<string>"
    }
  }
}

deal_pipeline_summary

Get pipeline KPIs for your team: open/won/lost counts and value, win rate, and value by stage

ArgumentTypeRequiredNotes
project_idstring–Filter by project ID, from list_projects
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "deal_pipeline_summary",
    "arguments": {}
  }
}

get_deal

Get a single deal by ID from your team

ArgumentTypeRequiredNotes
deal_idstringyesDeal ID, from list_deals
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "get_deal",
    "arguments": {
      "deal_id": "<string>"
    }
  }
}

list_deals

List deals for your team, optionally filtered by project or status

ArgumentTypeRequiredNotes
project_idstring–Filter by project ID, from list_projects
statusopen | won | lost–Filter by deal status
limitnumber–Items per page · 1–100 · default 20
offsetnumber–Items to skip. While pagination.hasMore, pass offset + limit for the next page · ≥ 0 · default 0
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "list_deals",
    "arguments": {}
  }
}

list_pipeline_stages

List the deal pipeline stages of a project, ordered by position

ArgumentTypeRequiredNotes
project_idstringyesProject ID, from list_projects
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "list_pipeline_stages",
    "arguments": {
      "project_id": "<string>"
    }
  }
}

move_deal_stage

Move a deal to a different pipeline stage (stamps won/lost when the target stage is an outcome stage)

ArgumentTypeRequiredNotes
deal_idstringyesDeal ID, from list_deals
stage_idstringyesTarget pipeline stage ID, from list_pipeline_stages
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "move_deal_stage",
    "arguments": {
      "deal_id": "<string>",
      "stage_id": "<string>"
    }
  }
}

Tasks

Work the task inbox: search, claim, complete, snooze and reassign tasks, run the rules that create them, and execute or review the messages they propose.

Scopes: tasks:read, tasks:write, messages:write

approve_review_task

Approve the message a campaign review step drafted, so the campaign sends it. Only for tasks of type review: find them with search_tasks and the filter {"type":{"eq":"review"}}. The message goes to a real person, so confirm the text with the user first; editedMessage replaces it.

ArgumentTypeRequiredNotes
idstringyesFrom search_tasks · 1–36 chars
editedMessagestring–1–10000 chars
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "approve_review_task",
    "arguments": {
      "id": "<string>"
    }
  }
}

cancel_task

Cancel a task

ArgumentTypeRequiredNotes
idstringyesFrom search_tasks · 1–36 chars
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "cancel_task",
    "arguments": {
      "id": "<string>"
    }
  }
}

claim_task

Claim a task for yourself. An API key claims on behalf of the user who created it. Claiming an already-claimed task is a conflict, not a silent reassignment.

ArgumentTypeRequiredNotes
idstringyesFrom search_tasks · 1–36 chars
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "claim_task",
    "arguments": {
      "id": "<string>"
    }
  }
}

complete_task

Complete a task. Marks it done without performing anything. To perform a LinkedIn task, use execute_task; to decide a review task, use approve_review_task or reject_review_task.

ArgumentTypeRequiredNotes
payloadobject–—
idstringyesFrom search_tasks · 1–36 chars
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "complete_task",
    "arguments": {
      "id": "<string>"
    }
  }
}

create_task

Create a task

ArgumentTypeRequiredNotes
contactIduuidyesFrom search_contacts
titlestringyes1–255 chars
descriptionstring–—
typelinkedin_message | linkedin_inmail | linkedin_connection | linkedin_comment | linkedin_visit | email | call | meeting | review | customyes—
prioritylow | medium | high | urgent–default "medium"
dueDatedate-time–—
dueTimestring–—
assignedTouuid–—
projectIduuid–From list_projects
actionPayloadobject–—
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "create_task",
    "arguments": {
      "contactId": "<uuid>",
      "title": "<string>",
      "type": "linkedin_message"
    }
  }
}

create_task_rule

Create a task rule

ArgumentTypeRequiredNotes
namestringyes1–255 chars
descriptionstring–—
isActiveboolean–default true
triggerSourcemanual | smart_review | flow_node | signal_reply | signal_connection | stage_change | stalled | webhook | ai_suggestyes—
triggerConfigobject–default {}
conditionsobject[]–default []
taskTemplateobjectyes—
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "create_task_rule",
    "arguments": {
      "name": "<string>",
      "triggerSource": "manual",
      "taskTemplate": {}
    }
  }
}

delete_task_rule

Delete a task rule Destructive: call without confirm first to preview.

ArgumentTypeRequiredNotes
idstringyesFrom search_task_rules · 1–36 chars
confirmstring–Token from this tool preview. Omit to preview; pass it back to execute.
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "delete_task_rule",
    "arguments": {
      "id": "<string>"
    }
  }
}

dry_run_task_rule

See what a rule would have matched, without creating anything. Replays the rule against the last 30 days of signal events and reports how many would have matched, with a sample. Creates nothing. Limited to once every 10 seconds per rule.

ArgumentTypeRequiredNotes
idstringyesFrom search_task_rules · 1–36 chars
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "dry_run_task_rule",
    "arguments": {
      "id": "<string>"
    }
  }
}

execute_task

Execute a task, performing its LinkedIn action. Sends the message, connection request or engagement the task describes. Subject to the sender cooldown and daily limits.

ArgumentTypeRequiredNotes
idstringyesFrom search_tasks · 1–36 chars
senderIduuidyesFrom search_senders
bodystring–1–10000 chars
subjectstring–1–200 chars
postUrnstring–—
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "execute_task",
    "arguments": {
      "id": "<string>",
      "senderId": "<uuid>"
    }
  }
}

get_task

Get a task

ArgumentTypeRequiredNotes
idstringyesFrom search_tasks · 1–36 chars
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "get_task",
    "arguments": {
      "id": "<string>"
    }
  }
}

get_task_rule

Get a task rule

ArgumentTypeRequiredNotes
idstringyesFrom search_task_rules · 1–36 chars
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "get_task_rule",
    "arguments": {
      "id": "<string>"
    }
  }
}

reassign_task

Reassign a task to another team member

ArgumentTypeRequiredNotes
idstringyesFrom search_tasks · 1–36 chars
assigneeIdstringyesTeam member user ID, as seen in a task assigned_to; null to unassign
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "reassign_task",
    "arguments": {
      "id": "<string>",
      "assigneeId": "<string>"
    }
  }
}

reject_review_task

Reject the message a campaign review step drafted, so it is never sent

ArgumentTypeRequiredNotes
idstringyesFrom search_tasks · 1–36 chars
reasonstringyes1–1000 chars
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "reject_review_task",
    "arguments": {
      "id": "<string>",
      "reason": "<string>"
    }
  }
}

search_task_rules

List task rules

ArgumentTypeRequiredNotes
page_sizestring–Items per page as a string, "1" to "200" (default 50); "0" returns only counts.total
cursorstring–pagination.next_cursor from the previous page; omit for the first page
sortstring–field or field:asc
filterstring–JSON object {field: {operator: value}}; a bare value means eq. Fields: is_active (boolean: eq, is_null). Example: {"is_active":{"eq":"..."}}
includeInactivestring–—
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "search_task_rules",
    "arguments": {}
  }
}

search_tasks

List tasks. Tasks of type review hold a message a campaign drafted and is waiting to send; approve_review_task or reject_review_task decides them.

ArgumentTypeRequiredNotes
page_sizestring–Items per page as a string, "1" to "100" (default 25); "0" returns only counts.total
cursorstring–pagination.next_cursor from the previous page; omit for the first page
sortstring–field or field:asc
filterstring–JSON object {field: {operator: value}}; a bare value means eq. Fields: status (enum pending
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "search_tasks",
    "arguments": {}
  }
}

snooze_task

Snooze a task until a given time

ArgumentTypeRequiredNotes
untildate-timeyes—
idstringyesFrom search_tasks · 1–36 chars
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "snooze_task",
    "arguments": {
      "until": "<iso-8601>",
      "id": "<string>"
    }
  }
}

update_task

Update a task

ArgumentTypeRequiredNotes
titlestring–1–255 chars
descriptionstring–—
typelinkedin_message | linkedin_inmail | linkedin_connection | linkedin_comment | linkedin_visit | email | call | meeting | review | custom–—
prioritylow | medium | high | urgent–—
statuspending | in_progress | completed | snoozed | cancelled | skipped–—
dueDatedate-time–—
dueTimestring–—
assignedTouuid–—
idstringyesFrom search_tasks · 1–36 chars
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "update_task",
    "arguments": {
      "id": "<string>"
    }
  }
}

update_task_rule

Update a task rule

ArgumentTypeRequiredNotes
namestring–1–255 chars
descriptionstring–—
isActiveboolean–—
triggerSourcemanual | smart_review | flow_node | signal_reply | signal_connection | stage_change | stalled | webhook | ai_suggest–—
triggerConfigobject–—
conditionsobject[]–—
taskTemplateobject–—
idstringyesFrom search_task_rules · 1–36 chars
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "update_task_rule",
    "arguments": {
      "id": "<string>"
    }
  }
}

Analytics

Aggregate contact, message and pipeline statistics.

Scopes: analytics:read

get_campaign_stats

Get campaign performance metrics for a specific campaign or all campaigns

ArgumentTypeRequiredNotes
campaign_iduuid–Campaign ID from list_campaigns, or omit for all
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "get_campaign_stats",
    "arguments": {}
  }
}

get_contact_stats

Get contact counts grouped by pipeline stage, tags, or overall totals for a project

ArgumentTypeRequiredNotes
project_iduuidyesProject ID, from list_projects
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "get_contact_stats",
    "arguments": {
      "project_id": "<uuid>"
    }
  }
}

get_message_stats

Get message volume statistics for the team over a time period

ArgumentTypeRequiredNotes
daysnumber–Number of days to look back · 1–90 · default 30
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "get_message_stats",
    "arguments": {}
  }
}

Bulk Operations

Run a change across many records as one tracked job: preview what it would touch, commit it, then watch or stop it.

Scopes: automations:write

act_on_bulk_operation

Commit, pause, resume, cancel or retry a bulk run. Cancelling stops the run; it does not reverse work already done, because a sent message does not unsend. retry_failed requeues only the items that failed.

ArgumentTypeRequiredNotes
iduuidyesFrom search_bulk_operations
actioncommit | pause | resume | cancel | retry_failedyes—
release_canaryboolean–—
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "act_on_bulk_operation",
    "arguments": {
      "id": "<uuid>",
      "action": "commit"
    }
  }
}

get_bulk_operation

Get a bulk run

ArgumentTypeRequiredNotes
iduuidyesFrom search_bulk_operations
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "get_bulk_operation",
    "arguments": {
      "id": "<uuid>"
    }
  }
}

preview_bulk_operation

Resolve what a bulk run would do, without doing it. Resolves the exact set of affected records and writes it down, so the number you approve is the number that runs. Nothing happens until the run is committed.

ArgumentTypeRequiredNotes
operationstringyesOne of: automation.enroll · min 1 chars
target_iduuid–What the run acts on; for automation.enroll, the campaign ID from list_campaigns
paramsobject–—
canary_sizeinteger–1–1000
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "preview_bulk_operation",
    "arguments": {
      "operation": "<string>"
    }
  }
}

search_bulk_items

List the items of a bulk run. Which records went through and which did not, with the machine-readable reason for each. The filter {"status":{"eq":"failed"}} answers the question you actually have after a partial run; page_size=0 answers how many without fetching them.

ArgumentTypeRequiredNotes
page_sizestring–Items per page as a string, "1" to "500" (default 100); "0" returns only counts.total
cursorstring–pagination.next_cursor from the previous page; omit for the first page
sortstring–field or field:asc
filterstring–JSON object {field: {operator: value}}; a bare value means eq. Fields: status (enum pending
iduuidyesFrom search_bulk_operations
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "search_bulk_items",
    "arguments": {
      "id": "<uuid>"
    }
  }
}

search_bulk_operations

List bulk runs

ArgumentTypeRequiredNotes
page_sizestring–Items per page as a string, "1" to "100" (default 25); "0" returns only counts.total
cursorstring–pagination.next_cursor from the previous page; omit for the first page
sortstring–field or field:asc
filterstring–JSON object {field: {operator: value}}; a bare value means eq. Fields: status (enum previewed
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "search_bulk_operations",
    "arguments": {}
  }
}

Discovery

Find out what this server can do without loading every tool schema: list toolsets, pull one group, or describe a single tool.

describe_tool

Return the description and input schema for a single tool by name, for when you know the tool but not its arguments.

ArgumentTypeRequiredNotes
namestringyesThe tool name, e.g. search_contacts.
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "describe_tool",
    "arguments": {
      "name": "<string>"
    }
  }
}

get_toolset_tools

Return the full description and input schema for every tool in one toolset. Use it after list_toolsets when you know which group you need, instead of carrying every schema on the server.

ArgumentTypeRequiredNotes
toolsetstringyesThe toolset name exactly as list_toolsets reported it.
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "get_toolset_tools",
    "arguments": {
      "toolset": "<string>"
    }
  }
}

list_toolsets

List the tool groups this server exposes, with a one-line summary and the scopes each needs. Start here: it is far cheaper than loading every tool schema, and it tells you which toolset to pull with get_toolset_tools.

No arguments.

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "list_toolsets",
    "arguments": {}
  }
}

Server

Server liveness and version.

mcp_health

Get MCP server health status including version and uptime

No arguments.

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "mcp_health",
    "arguments": {}
  }
}

POST /mcp — MCP JSON-RPC request

Streamable HTTP transport for the Model Context Protocol. Send a JSON-RPC 2.0 envelope (initialize, tools/list, tools/call, resources/read, prompts/get). The first initialize call returns an Mcp-Session-Id header that must be echoed on every subsequent request. Responses are application/json or text/event-stream depending on the Accept header.

GET /mcp — MCP event stream

Opens the server-to-client SSE stream for an existing session. Requires the Mcp-Session-Id header.

DELETE /mcp — Terminate MCP session

Closes the session identified by Mcp-Session-Id and releases its transport.