POST /contacts/search — Search contacts
Advanced search with filters for tags, stages, and text query. Searches in firstName, lastName, title, linkedin, and email.
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.
Contact management operations
POST /contacts/search — Search contactsAdvanced search with filters for tags, stages, and text query. Searches in firstName, lastName, title, linkedin, and email.
GET /contacts/{id}/messages — Get contact messagesReturns messages for a specific contact
GET /contacts/{id}/drafts — List drafts for a contactReturns 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 contactCreates 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.
LinkedIn message operations
POST /contacts/{id}/messages — Send a message to a contactQueues 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.
Automation sequence operations
POST /automations — Create a campaignBuilds 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 automationsReturns a paginated list of automations
GET /automations/{id} — Get automationReturns 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 contactsEnrolls contacts into an automation sequence
POST /automations/{id}/unenroll — Unenroll contactsRemoves contacts from an automation sequence
PUT /automations/{id}/status — Update automation statusPause or resume an automation
Webhook endpoint management
POST /webhooks/{id}/test — Send a test deliveryQueues 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.
Project management (read-only)
GET /projects — List projectsReturns a paginated list of projects
Contact list management
GET /lists — List contact listsReturns a paginated list of contact lists. Optionally filter by project.
POST /lists — Create contact listCreates a new contact list and associates it with a project
Deal pipeline management
GET /deals — List dealsReturns a paginated list of deals, most recently updated first. Optionally filter by project and status.
POST /deals — Create dealCreates 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 summaryReturns open / won / lost counts and values, the win rate, and a per-stage breakdown.
GET /deals/{id} — Get dealPATCH /deals/{id} — Update dealApplies an outcome action (lost / won / reopen) or edits deal fields. Returns the deal as it stands after the change.
DELETE /deals/{id} — Delete dealSoft deletes a deal
Task inbox, creation, and lifecycle actions (claim / snooze / complete / reassign / execute / review).
POST /tasks/webhook — Fire a webhook-source task ruleExternal systems POST here to fire rules whose triggerSource is "webhook". Enforces the team kill-switch and the rule's conditions.
AI reply drafts a human reviews before anything reaches a real person.
GET /drafts/{draftId} — Get a draftDELETE /drafts/{draftId} — Discard a draftDiscards a pending or edited draft.
POST /drafts/{draftId}/approve — Approve a draft and place it in the API queuePlaces 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.
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.
POST /contacts/{id}/messages · scope messages:write. Opens the conversation when there is none yet.POST /drafts/{draftId}/approve · scope messages:write.POST /connection-requests · scope connections:write. An optional note, ≤300 characters.POST /profile-visits · scope engagements:write. Shows up in their “who viewed your profile”. A repeat visit the same day costs nothing.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."}'
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}'
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.
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 requestQueues 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 visitQueues 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 postQueues 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.
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
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.
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"
}
}
}
}
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"}'
POST https://sprootal.com/api/mcp carries JSON-RPC requests, GET opens the server-to-client SSE stream, DELETE terminates a session.Mcp-Session-Id response header identifies the session. A session expires after 30 minutes idle or 4 hours total, whichever comes first.X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset. A 401 carries a WWW-Authenticate header pointing at the resource metadata document.A tool call returns permission_denied when the key or token lacks the scope it needs. OAuth consent is granted in groups:
| Consent group | Scopes |
|---|---|
| Read your contacts and companies | contacts:read, companies:read, lists:read, tags:read |
| Update contacts and companies | contacts:write, companies:write, lists:write, tags:write |
| Read conversations and drafts | messages:read |
| Prepare message drafts | messages:write |
| Send LinkedIn connection requests | connections:write |
| Visit profiles and like posts | engagements:write |
| Read and manage campaigns | automations:read, automations:write |
| Read pipeline and reports | deals:read, analytics:read, projects:read, sender_profiles:read |
| Update pipeline and projects | deals:write, projects:write, sender_profiles:write |
| Read and manage tasks | tasks:read, tasks:write |
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.
Create, search, update and delete contacts, and manage their list memberships.
Scopes: contacts:read, contacts:write, lists:read, lists:write
create_contactCreate 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.
| Argument | Type | Required | Notes |
|---|---|---|---|
firstName | string | yes | 1–100 chars |
lastName | string | yes | 1–100 chars |
title | string | – | max 200 chars |
linkedin | string | – | max 255 chars |
attendeeProviderId | string | – | max 255 chars |
projectId | uuid | – | From list_projects |
listId | uuid | – | From list_lists |
customFields | object | – | — |
email | – | max 255 chars | |
pipelineStage | string | – | 1–255 chars |
updateIfExists | boolean | – | default true |
moveToList | boolean | – | default true |
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "create_contact",
"arguments": {
"firstName": "<string>",
"lastName": "<string>"
}
}
}
delete_contactRemove a contact. A soft delete: the contact stops appearing in every read, and support can restore it.
| Argument | Type | Required | Notes |
|---|---|---|---|
id | string | yes | From search_contacts · 1–36 chars |
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "delete_contact",
"arguments": {
"id": "<string>"
}
}
}
get_contactGet one contact, with its profile, engagement and conversations
| Argument | Type | Required | Notes |
|---|---|---|---|
id | string | yes | From search_contacts · 1–36 chars |
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "get_contact",
"arguments": {
"id": "<string>"
}
}
}
search_contactsList contacts. Scoped to a project with project_id, which applies that project visibility rules. q matches name, LinkedIn slug and provider id.
| Argument | Type | Required | Notes |
|---|---|---|---|
project_id | uuid | – | From list_projects |
q | string | – | max 200 chars |
tag_ids | uuid[] | – | Tag IDs, from list_tags |
stage_ids | uuid[] | – | Pipeline stage IDs, from list_pipeline_stages |
page_size | string | – | Items per page as a string, "1" to "100" (default 20); "0" returns only counts.total |
cursor | string | – | pagination.next_cursor from the previous page; omit for the first page |
sort | string | – | field or field:asc |
filter | string | – | 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_contactChange a contact details
| Argument | Type | Required | Notes |
|---|---|---|---|
id | string | yes | From search_contacts · 1–36 chars |
firstName | string | – | 1–100 chars |
lastName | string | – | 1–100 chars |
title | string | – | — |
linkedin | string | – | — |
projectId | uuid | – | From list_projects |
customFields | object | – | — |
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "update_contact",
"arguments": {
"id": "<string>"
}
}
}
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_itemCancel a queued message before it sends
| Argument | Type | Required | Notes |
|---|---|---|---|
itemId | string | yes | From search_message_queue · 1–64 chars |
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "cancel_message_queue_item",
"arguments": {
"itemId": "<string>"
}
}
}
get_messageGet one message
| Argument | Type | Required | Notes |
|---|---|---|---|
id | string | yes | From search_messages · 1–64 chars |
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "get_message",
"arguments": {
"id": "<string>"
}
}
}
like_postQueue 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.
| Argument | Type | Required | Notes |
|---|---|---|---|
contact_id | uuid | yes | Whose post it is, so the like is attributable (search_contacts) |
sender_id | uuid | yes | Which sender profile does the liking (search_senders) |
post_url | string | yes | A 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_queueList queued outbound actions. Covers every queued action type, not just messages. Filter with {"action_type":{"eq":"send_connection"}}.
| Argument | Type | Required | Notes |
|---|---|---|---|
page_size | string | – | Items per page as a string, "1" to "100" (default 20); "0" returns only counts.total |
cursor | string | – | pagination.next_cursor from the previous page; omit for the first page |
sort | string | – | field or field:asc |
filter | string | – | 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_messagesList messages across the team. Narrow with contact_id, sender_profile_id or type. Newest first, cursor-paged.
| Argument | Type | Required | Notes |
|---|---|---|---|
contact_id | uuid | – | From search_contacts |
sender_profile_id | uuid | – | From search_senders |
type | inbox | outbox | – | — |
page_size | string | – | Items per page as a string, "1" to "100" (default 20); "0" returns only counts.total |
cursor | string | – | pagination.next_cursor from the previous page; omit for the first page |
sort | string | – | field or field:asc |
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "search_messages",
"arguments": {}
}
}
send_connection_requestQueue 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.
| Argument | Type | Required | Notes |
|---|---|---|---|
contact_id | uuid | yes | The contact id, from search_contacts |
sender_id | uuid | yes | Which sender profile the invitation comes from (search_senders) |
note | string | – | 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_reached | boolean | – | 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_hours | integer | – | 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_messageQueue 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.
| Argument | Type | Required | Notes |
|---|---|---|---|
contact_id | uuid | yes | The contact id, from search_contacts |
sender_id | uuid | yes | Which sender profile the message comes from (search_senders) |
text | string | yes | The 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_replied | boolean | – | 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_itemEdit 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.
| Argument | Type | Required | Notes |
|---|---|---|---|
itemId | string | yes | From search_message_queue · 1–64 chars |
text | string | – | 1–8000 chars |
move | top | bottom | – | — |
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "update_message_queue_item",
"arguments": {
"itemId": "<string>"
}
}
}
visit_profileQueue 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.
| Argument | Type | Required | Notes |
|---|---|---|---|
contact_id | uuid | yes | The contact id, from search_contacts |
sender_id | uuid | yes | Which 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_requestQueue 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.
| Argument | Type | Required | Notes |
|---|---|---|---|
contact_id | uuid | yes | The contact who holds the invitation, from search_contacts |
sender_id | uuid | yes | Which 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>"
}
}
}
Create, list and discard AI reply drafts for a conversation.
Scopes: messages:read, messages:write
create_draftWrite 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.
| Argument | Type | Required | Notes |
|---|---|---|---|
contact_id | uuid | yes | The contact id, from search_contacts |
sender_id | uuid | yes | Which sender profile the reply comes from (search_senders) |
text | string | yes | The proposed reply · 1–8000 chars |
agent_name | string | – | Who wrote it, shown on the draft card · max 100 chars |
playbook_label | string | – | Template name this reply follows; a matching label lets the human regenerate against that template · max 100 chars |
confidence | number | – | 0 to 1, shown to the reviewer · 0–1 |
meta | object | – | 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_draftWithdraw a draft you created so it stops competing for the reviewer attention. Fails if a human already sent or discarded it.
| Argument | Type | Required | Notes |
|---|---|---|---|
draft_id | uuid | yes | Draft ID, from list_drafts |
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "discard_draft",
"arguments": {
"draft_id": "<uuid>"
}
}
}
list_draftsList reply drafts in a conversation, including ones a human already sent or discarded
| Argument | Type | Required | Notes |
|---|---|---|---|
contact_id | uuid | yes | The contact id, from search_contacts |
sender_id | uuid | yes | Which sender profile the conversation is with (search_senders) |
status | pending | edited | queued | sent | declined | obsolete[] | – | Defaults to drafts still awaiting review |
limit | number | – | Items per page · 1–100 · default 50 |
offset | number | – | 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>"
}
}
}
Manage outreach campaigns: create, enrol and unenrol contacts, change status, read stats.
Scopes: automations:read, automations:write
create_campaignCreate 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.
| Argument | Type | Required | Notes |
|---|---|---|---|
name | string | yes | Campaign name · 1–200 chars |
description | string | yes | Who 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_id | uuid | yes | Project the campaign belongs to, from list_projects |
blocks | object[] | – | 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 |
prompt | string | – | Free-text brief the campaign flow is generated from. Spends AI tokens. Mutually exclusive with blocks. · 1–2000 chars |
service_id | string | – | Service whose context steers generation, for the prompt path · min 1 chars |
scenario_id | string | – | 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_campaignDelete 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.
| Argument | Type | Required | Notes |
|---|---|---|---|
campaign_id | uuid | yes | Campaign ID, from list_campaigns |
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "delete_campaign",
"arguments": {
"campaign_id": "<uuid>"
}
}
}
enroll_contactsEnroll 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.
| Argument | Type | Required | Notes |
|---|---|---|---|
campaign_id | uuid | yes | Campaign ID, from list_campaigns |
contact_ids | uuid[] | yes | Contact 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_campaignGet 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
| Argument | Type | Required | Notes |
|---|---|---|---|
campaign_id | uuid | yes | Campaign ID, from list_campaigns |
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "get_campaign",
"arguments": {
"campaign_id": "<uuid>"
}
}
}
list_campaignsList campaigns with optional status and project filters
| Argument | Type | Required | Notes |
|---|---|---|---|
status | string | – | Filter by status (draft, active, paused, completed) |
project_id | uuid | – | Filter by project ID, from list_projects |
limit | number | – | Items per page · 1–100 · default 20 |
offset | number | – | 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_contactsRemove contacts from a campaign sequence
| Argument | Type | Required | Notes |
|---|---|---|---|
campaign_id | uuid | yes | Campaign ID, from list_campaigns |
contact_ids | uuid[] | yes | Contact 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_campaignEdit 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.
| Argument | Type | Required | Notes |
|---|---|---|---|
campaign_id | uuid | yes | Campaign ID, from list_campaigns |
name | string | – | New name · 1–200 chars |
description | string | – | New description of who the campaign targets and why · 20–1000 chars |
blocks | object[] | – | New step chain, replacing every current step. Same rules as create_campaign. Mutually exclusive with prompt. |
prompt | string | – | New brief to regenerate every step from. Mutually exclusive with blocks. · 1–2000 chars |
service_id | string | – | Service whose context steers generation, for prompt · min 1 chars |
scenario_id | string | – | 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_statusActivate 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.
| Argument | Type | Required | Notes |
|---|---|---|---|
campaign_id | uuid | yes | Campaign ID, from list_campaigns |
status | active | paused | yes | New status |
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "update_campaign_status",
"arguments": {
"campaign_id": "<uuid>",
"status": "active"
}
}
}
Read projects and their pipeline stages, and list or create project custom fields.
Scopes: projects:read, projects:write
add_contacts_to_listAdd contacts to a list
| Argument | Type | Required | Notes |
|---|---|---|---|
list_id | uuid | yes | List ID, from list_lists |
contact_ids | uuid[] | yes | Contact 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_fieldCreate 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.
| Argument | Type | Required | Notes |
|---|---|---|---|
id | uuid | yes | Project id, from list_projects |
fieldName | string | yes | 1–100 chars |
fieldKey | string | – | 1–100 chars |
fieldType | text | number | date | select | multi_select | boolean | url | email | phone | currency | percent | yes | — |
description | string | – | max 500 chars |
isRequired | boolean | – | default false |
defaultValue | string | – | max 1000 chars |
options | object | – | — |
validation | object | – | — |
currencyCode | string | – | 3–3 chars |
order | integer | – | 0–9007199254740991 |
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "create_custom_field",
"arguments": {
"id": "<uuid>",
"fieldName": "<string>",
"fieldType": "text"
}
}
}
create_listCreate a new contact list
| Argument | Type | Required | Notes |
|---|---|---|---|
name | string | yes | List name · 1–100 chars |
project_id | uuid | – | Associate with a project, from list_projects |
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "create_list",
"arguments": {
"name": "<string>"
}
}
}
list_listsList contact lists, optionally filtered by project
| Argument | Type | Required | Notes |
|---|---|---|---|
project_id | uuid | – | Filter by project ID, from list_projects |
limit | number | – | Items per page · 1–100 · default 20 |
offset | number | – | 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_projectsList all projects
| Argument | Type | Required | Notes |
|---|---|---|---|
limit | number | – | Items per page · 1–100 · default 20 |
offset | number | – | 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_listRemove contacts from a list
| Argument | Type | Required | Notes |
|---|---|---|---|
list_id | uuid | yes | List ID, from list_lists |
contact_ids | uuid[] | yes | Contact 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_fieldsList 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.
| Argument | Type | Required | Notes |
|---|---|---|---|
id | uuid | yes | Project id, from list_projects |
page_size | string | – | Items per page as a string, "1" to "100" (default 50); "0" returns only counts.total |
cursor | string | – | pagination.next_cursor from the previous page; omit for the first page |
sort | string | – | field or field:asc |
filter | string | – | 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>"
}
}
}
Read connected LinkedIn sender accounts and their limits.
Scopes: sender_profiles:read
check_connectionsCheck 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.
| Argument | Type | Required | Notes |
|---|---|---|---|
identifiers | string[] | yes | LinkedIn profile URLs, slugs or member URNs |
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "check_connections",
"arguments": {
"identifiers": [
"<string>"
]
}
}
}
get_senderGet one sender, with its full limits, sync state and mailbox counts
| Argument | Type | Required | Notes |
|---|---|---|---|
id | string | yes | From search_senders · 1–64 chars |
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "get_sender",
"arguments": {
"id": "<string>"
}
}
}
get_sender_cooldownCheck whether a sender is cooling down. Ask before executing: a sender inside its cooldown refuses the send, and this says how long to wait.
| Argument | Type | Required | Notes |
|---|---|---|---|
id | string | yes | From search_senders · 1–64 chars |
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "get_sender_cooldown",
"arguments": {
"id": "<string>"
}
}
}
get_sender_daily_limitCheck a sender daily allowance for one action type
| Argument | Type | Required | Notes |
|---|---|---|---|
id | string | yes | From search_senders · 1–64 chars |
actionType | send_connection | send_message | send_inmail | visit_profile | like_post | comment_post | withdraw_connection | yes | — |
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "get_sender_daily_limit",
"arguments": {
"id": "<string>",
"actionType": "send_connection"
}
}
}
search_sendersList 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.
| Argument | Type | Required | Notes |
|---|---|---|---|
page_size | string | – | Items per page as a string, "1" to "100" (default 20); "0" returns only counts.total |
cursor | string | – | pagination.next_cursor from the previous page; omit for the first page |
sort | string | – | field or field:asc |
filter | string | – | 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": {}
}
}
Manage webhook endpoints and inspect delivery attempts for your team.
Scopes: webhooks:manage
act_on_webhookRotate 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.
| Argument | Type | Required | Notes |
|---|---|---|---|
id | string | yes | From search_webhooks · 1–64 chars |
action | rotate_secret | enable | yes | — |
grace_seconds | integer | – | 0–604800 |
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "act_on_webhook",
"arguments": {
"id": "<string>",
"action": "rotate_secret"
}
}
}
create_webhookRegister 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.
| Argument | Type | Required | Notes |
|---|---|---|---|
name | string | yes | 1–100 chars |
url | uri | yes | — |
events | lead.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_webhookPermanently 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.
| Argument | Type | Required | Notes |
|---|---|---|---|
id | string | yes | From search_webhooks · 1–64 chars |
confirm | string | – | 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_webhookGet one webhook endpoint, with its delivery health
| Argument | Type | Required | Notes |
|---|---|---|---|
id | string | yes | From search_webhooks · 1–64 chars |
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "get_webhook",
"arguments": {
"id": "<string>"
}
}
}
get_webhook_deliveryGet one delivery attempt, with its payload and response
| Argument | Type | Required | Notes |
|---|---|---|---|
id | string | yes | From search_webhooks · 1–64 chars |
deliveryId | string | yes | From 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_deliverySend a past delivery again, to the endpoint it was for
| Argument | Type | Required | Notes |
|---|---|---|---|
id | string | yes | From search_webhooks · 1–64 chars |
deliveryId | string | yes | From 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_deliveriesList 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.
| Argument | Type | Required | Notes |
|---|---|---|---|
id | string | yes | From search_webhooks · 1–64 chars |
page_size | string | – | Items per page as a string, "1" to "200" (default 50); "0" returns only counts.total |
cursor | string | – | pagination.next_cursor from the previous page; omit for the first page |
sort | string | – | field or field:asc |
filter | string | – | 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_webhooksList webhook endpoints
| Argument | Type | Required | Notes |
|---|---|---|---|
page_size | string | – | Items per page as a string, "1" to "200" (default 50); "0" returns only counts.total |
cursor | string | – | pagination.next_cursor from the previous page; omit for the first page |
sort | string | – | field or field:asc |
filter | string | – | 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_webhookChange an endpoint name, URL, events or enabled state
| Argument | Type | Required | Notes |
|---|---|---|---|
id | string | yes | From search_webhooks · 1–64 chars |
name | string | – | 1–100 chars |
url | uri | – | — |
events | lead.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[] | – | — |
enabled | boolean | – | — |
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "update_webhook",
"arguments": {
"id": "<string>"
}
}
}
Create project tags and attach or detach them from contacts.
Scopes: tags:read, tags:write
add_tag_to_contactAdd a tag to a contact in a project
| Argument | Type | Required | Notes |
|---|---|---|---|
project_id | uuid | yes | Project ID, from list_projects |
contact_id | uuid | yes | Contact ID, from search_contacts |
tag_id | uuid | yes | Tag 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_tagCreate a new tag in a project
| Argument | Type | Required | Notes |
|---|---|---|---|
project_id | uuid | yes | Project ID, from list_projects |
name | string | yes | Tag name · 1–100 chars |
text_color | string | – | Hex text color · max 7 chars |
background_color | string | – | 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_tagsList tags for a project
| Argument | Type | Required | Notes |
|---|---|---|---|
project_id | uuid | yes | Project ID, from list_projects |
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "list_tags",
"arguments": {
"project_id": "<uuid>"
}
}
}
remove_tag_from_contactRemove a tag from a contact in a project
| Argument | Type | Required | Notes |
|---|---|---|---|
project_id | uuid | yes | Project ID, from list_projects |
contact_id | uuid | yes | Contact ID, from search_contacts |
tag_id | uuid | yes | Tag 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>"
}
}
}
Read, create and update company records.
Scopes: companies:read, companies:write
create_companyCreate 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.
| Argument | Type | Required | Notes |
|---|---|---|---|
name | string | yes | 1–255 chars |
website | string | – | max 255 chars |
domain | string | – | max 255 chars |
linkedinCompanyId | string | – | max 64 chars |
linkedin | string | – | max 255 chars |
industry | string | – | max 255 chars |
size | string | – | max 50 chars |
market | string | – | max 50 chars |
phoneNumber | string | – | max 100 chars |
email | – | max 255 chars | |
address | string | – | max 255 chars |
description | string | – | — |
type | string | – | max 50 chars |
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "create_company",
"arguments": {
"name": "<string>"
}
}
}
get_companyGet 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.
| Argument | Type | Required | Notes |
|---|---|---|---|
id | uuid | yes | From search_companies |
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "get_company",
"arguments": {
"id": "<uuid>"
}
}
}
search_companiesList companies
| Argument | Type | Required | Notes |
|---|---|---|---|
page_size | string | – | Items per page as a string, "1" to "100" (default 20); "0" returns only counts.total |
cursor | string | – | pagination.next_cursor from the previous page; omit for the first page |
sort | string | – | field or field:asc |
filter | string | – | 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_companyChange a company details. Renaming a company also updates the company shown on every contact linked to it.
| Argument | Type | Required | Notes |
|---|---|---|---|
id | uuid | yes | From search_companies |
name | string | – | 1–255 chars |
domain | string | – | — |
website | string | – | — |
linkedin | string | – | — |
industry | string | – | — |
size | string | – | — |
market | string | – | — |
phoneNumber | string | – | — |
email | – | — | |
address | string | – | — |
description | string | – | — |
type | string | – | — |
status | active | inactive | archived | – | — |
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "update_company",
"arguments": {
"id": "<uuid>"
}
}
}
Read the deal pipeline, create deals and move them between stages.
Scopes: deals:read, deals:write
create_dealCreate a new deal for a contact in a project pipeline stage
| Argument | Type | Required | Notes |
|---|---|---|---|
project_id | string | yes | Project ID, from list_projects |
contact_id | string | yes | Primary contact ID, from search_contacts |
contact_ids | string[] | – | All contact IDs on the deal (first = primary), from search_contacts |
stage_id | string | yes | Pipeline stage ID, from list_pipeline_stages |
company_id | string | – | Associated company ID, from search_companies |
title | string | – | max 255 chars |
amount_minor | integer | – | Amount in minor units (e.g. cents) · 0–9007199254740991 |
currency | string | – | ISO 4217 currency code · 3–3 chars |
priority | high | normal | low | – | — |
owner_id | string | – | Owning team member ID |
expected_close_date | string | – | 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_summaryGet pipeline KPIs for your team: open/won/lost counts and value, win rate, and value by stage
| Argument | Type | Required | Notes |
|---|---|---|---|
project_id | string | – | Filter by project ID, from list_projects |
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "deal_pipeline_summary",
"arguments": {}
}
}
get_dealGet a single deal by ID from your team
| Argument | Type | Required | Notes |
|---|---|---|---|
deal_id | string | yes | Deal ID, from list_deals |
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "get_deal",
"arguments": {
"deal_id": "<string>"
}
}
}
list_dealsList deals for your team, optionally filtered by project or status
| Argument | Type | Required | Notes |
|---|---|---|---|
project_id | string | – | Filter by project ID, from list_projects |
status | open | won | lost | – | Filter by deal status |
limit | number | – | Items per page · 1–100 · default 20 |
offset | number | – | 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_stagesList the deal pipeline stages of a project, ordered by position
| Argument | Type | Required | Notes |
|---|---|---|---|
project_id | string | yes | Project ID, from list_projects |
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "list_pipeline_stages",
"arguments": {
"project_id": "<string>"
}
}
}
move_deal_stageMove a deal to a different pipeline stage (stamps won/lost when the target stage is an outcome stage)
| Argument | Type | Required | Notes |
|---|---|---|---|
deal_id | string | yes | Deal ID, from list_deals |
stage_id | string | yes | Target 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>"
}
}
}
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_taskApprove 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.
| Argument | Type | Required | Notes |
|---|---|---|---|
id | string | yes | From search_tasks · 1–36 chars |
editedMessage | string | – | 1–10000 chars |
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "approve_review_task",
"arguments": {
"id": "<string>"
}
}
}
cancel_taskCancel a task
| Argument | Type | Required | Notes |
|---|---|---|---|
id | string | yes | From search_tasks · 1–36 chars |
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "cancel_task",
"arguments": {
"id": "<string>"
}
}
}
claim_taskClaim 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.
| Argument | Type | Required | Notes |
|---|---|---|---|
id | string | yes | From search_tasks · 1–36 chars |
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "claim_task",
"arguments": {
"id": "<string>"
}
}
}
complete_taskComplete 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.
| Argument | Type | Required | Notes |
|---|---|---|---|
payload | object | – | — |
id | string | yes | From search_tasks · 1–36 chars |
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "complete_task",
"arguments": {
"id": "<string>"
}
}
}
create_taskCreate a task
| Argument | Type | Required | Notes |
|---|---|---|---|
contactId | uuid | yes | From search_contacts |
title | string | yes | 1–255 chars |
description | string | – | — |
type | linkedin_message | linkedin_inmail | linkedin_connection | linkedin_comment | linkedin_visit | email | call | meeting | review | custom | yes | — |
priority | low | medium | high | urgent | – | default "medium" |
dueDate | date-time | – | — |
dueTime | string | – | — |
assignedTo | uuid | – | — |
projectId | uuid | – | From list_projects |
actionPayload | object | – | — |
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "create_task",
"arguments": {
"contactId": "<uuid>",
"title": "<string>",
"type": "linkedin_message"
}
}
}
create_task_ruleCreate a task rule
| Argument | Type | Required | Notes |
|---|---|---|---|
name | string | yes | 1–255 chars |
description | string | – | — |
isActive | boolean | – | default true |
triggerSource | manual | smart_review | flow_node | signal_reply | signal_connection | stage_change | stalled | webhook | ai_suggest | yes | — |
triggerConfig | object | – | default {} |
conditions | object[] | – | default [] |
taskTemplate | object | yes | — |
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "create_task_rule",
"arguments": {
"name": "<string>",
"triggerSource": "manual",
"taskTemplate": {}
}
}
}
delete_task_ruleDelete a task rule Destructive: call without confirm first to preview.
| Argument | Type | Required | Notes |
|---|---|---|---|
id | string | yes | From search_task_rules · 1–36 chars |
confirm | string | – | 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_ruleSee 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.
| Argument | Type | Required | Notes |
|---|---|---|---|
id | string | yes | From search_task_rules · 1–36 chars |
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "dry_run_task_rule",
"arguments": {
"id": "<string>"
}
}
}
execute_taskExecute a task, performing its LinkedIn action. Sends the message, connection request or engagement the task describes. Subject to the sender cooldown and daily limits.
| Argument | Type | Required | Notes |
|---|---|---|---|
id | string | yes | From search_tasks · 1–36 chars |
senderId | uuid | yes | From search_senders |
body | string | – | 1–10000 chars |
subject | string | – | 1–200 chars |
postUrn | string | – | — |
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "execute_task",
"arguments": {
"id": "<string>",
"senderId": "<uuid>"
}
}
}
get_taskGet a task
| Argument | Type | Required | Notes |
|---|---|---|---|
id | string | yes | From search_tasks · 1–36 chars |
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "get_task",
"arguments": {
"id": "<string>"
}
}
}
get_task_ruleGet a task rule
| Argument | Type | Required | Notes |
|---|---|---|---|
id | string | yes | From search_task_rules · 1–36 chars |
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "get_task_rule",
"arguments": {
"id": "<string>"
}
}
}
reassign_taskReassign a task to another team member
| Argument | Type | Required | Notes |
|---|---|---|---|
id | string | yes | From search_tasks · 1–36 chars |
assigneeId | string | yes | Team 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_taskReject the message a campaign review step drafted, so it is never sent
| Argument | Type | Required | Notes |
|---|---|---|---|
id | string | yes | From search_tasks · 1–36 chars |
reason | string | yes | 1–1000 chars |
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "reject_review_task",
"arguments": {
"id": "<string>",
"reason": "<string>"
}
}
}
search_task_rulesList task rules
| Argument | Type | Required | Notes |
|---|---|---|---|
page_size | string | – | Items per page as a string, "1" to "200" (default 50); "0" returns only counts.total |
cursor | string | – | pagination.next_cursor from the previous page; omit for the first page |
sort | string | – | field or field:asc |
filter | string | – | JSON object {field: {operator: value}}; a bare value means eq. Fields: is_active (boolean: eq, is_null). Example: {"is_active":{"eq":"..."}} |
includeInactive | string | – | — |
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "search_task_rules",
"arguments": {}
}
}
search_tasksList 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.
| Argument | Type | Required | Notes |
|---|---|---|---|
page_size | string | – | Items per page as a string, "1" to "100" (default 25); "0" returns only counts.total |
cursor | string | – | pagination.next_cursor from the previous page; omit for the first page |
sort | string | – | field or field:asc |
filter | string | – | 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_taskSnooze a task until a given time
| Argument | Type | Required | Notes |
|---|---|---|---|
until | date-time | yes | — |
id | string | yes | From search_tasks · 1–36 chars |
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "snooze_task",
"arguments": {
"until": "<iso-8601>",
"id": "<string>"
}
}
}
update_taskUpdate a task
| Argument | Type | Required | Notes |
|---|---|---|---|
title | string | – | 1–255 chars |
description | string | – | — |
type | linkedin_message | linkedin_inmail | linkedin_connection | linkedin_comment | linkedin_visit | email | call | meeting | review | custom | – | — |
priority | low | medium | high | urgent | – | — |
status | pending | in_progress | completed | snoozed | cancelled | skipped | – | — |
dueDate | date-time | – | — |
dueTime | string | – | — |
assignedTo | uuid | – | — |
id | string | yes | From search_tasks · 1–36 chars |
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "update_task",
"arguments": {
"id": "<string>"
}
}
}
update_task_ruleUpdate a task rule
| Argument | Type | Required | Notes |
|---|---|---|---|
name | string | – | 1–255 chars |
description | string | – | — |
isActive | boolean | – | — |
triggerSource | manual | smart_review | flow_node | signal_reply | signal_connection | stage_change | stalled | webhook | ai_suggest | – | — |
triggerConfig | object | – | — |
conditions | object[] | – | — |
taskTemplate | object | – | — |
id | string | yes | From search_task_rules · 1–36 chars |
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "update_task_rule",
"arguments": {
"id": "<string>"
}
}
}
Aggregate contact, message and pipeline statistics.
Scopes: analytics:read
get_campaign_statsGet campaign performance metrics for a specific campaign or all campaigns
| Argument | Type | Required | Notes |
|---|---|---|---|
campaign_id | uuid | – | 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_statsGet contact counts grouped by pipeline stage, tags, or overall totals for a project
| Argument | Type | Required | Notes |
|---|---|---|---|
project_id | uuid | yes | Project ID, from list_projects |
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "get_contact_stats",
"arguments": {
"project_id": "<uuid>"
}
}
}
get_message_statsGet message volume statistics for the team over a time period
| Argument | Type | Required | Notes |
|---|---|---|---|
days | number | – | Number of days to look back · 1–90 · default 30 |
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "get_message_stats",
"arguments": {}
}
}
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_operationCommit, 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.
| Argument | Type | Required | Notes |
|---|---|---|---|
id | uuid | yes | From search_bulk_operations |
action | commit | pause | resume | cancel | retry_failed | yes | — |
release_canary | boolean | – | — |
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "act_on_bulk_operation",
"arguments": {
"id": "<uuid>",
"action": "commit"
}
}
}
get_bulk_operationGet a bulk run
| Argument | Type | Required | Notes |
|---|---|---|---|
id | uuid | yes | From search_bulk_operations |
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "get_bulk_operation",
"arguments": {
"id": "<uuid>"
}
}
}
preview_bulk_operationResolve 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.
| Argument | Type | Required | Notes |
|---|---|---|---|
operation | string | yes | One of: automation.enroll · min 1 chars |
target_id | uuid | – | What the run acts on; for automation.enroll, the campaign ID from list_campaigns |
params | object | – | — |
canary_size | integer | – | 1–1000 |
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "preview_bulk_operation",
"arguments": {
"operation": "<string>"
}
}
}
search_bulk_itemsList 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.
| Argument | Type | Required | Notes |
|---|---|---|---|
page_size | string | – | Items per page as a string, "1" to "500" (default 100); "0" returns only counts.total |
cursor | string | – | pagination.next_cursor from the previous page; omit for the first page |
sort | string | – | field or field:asc |
filter | string | – | JSON object {field: {operator: value}}; a bare value means eq. Fields: status (enum pending |
id | uuid | yes | From search_bulk_operations |
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "search_bulk_items",
"arguments": {
"id": "<uuid>"
}
}
}
search_bulk_operationsList bulk runs
| Argument | Type | Required | Notes |
|---|---|---|---|
page_size | string | – | Items per page as a string, "1" to "100" (default 25); "0" returns only counts.total |
cursor | string | – | pagination.next_cursor from the previous page; omit for the first page |
sort | string | – | field or field:asc |
filter | string | – | 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": {}
}
}
Find out what this server can do without loading every tool schema: list toolsets, pull one group, or describe a single tool.
describe_toolReturn the description and input schema for a single tool by name, for when you know the tool but not its arguments.
| Argument | Type | Required | Notes |
|---|---|---|---|
name | string | yes | The tool name, e.g. search_contacts. |
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "describe_tool",
"arguments": {
"name": "<string>"
}
}
}
get_toolset_toolsReturn 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.
| Argument | Type | Required | Notes |
|---|---|---|---|
toolset | string | yes | The 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_toolsetsList 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 liveness and version.
mcp_healthGet 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 requestStreamable 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 streamOpens the server-to-client SSE stream for an existing session. Requires the Mcp-Session-Id header.
DELETE /mcp — Terminate MCP sessionCloses the session identified by Mcp-Session-Id and releases its transport.