Developer docs
Vendo AI API and MCP server
Everything you can do in the Vendo AI app, you can also do from code. Call the REST API from your own backend, or connect Claude, ChatGPT or Cursor to our MCP server and run agents, Vendo SDR, campaigns and the CRM by simply asking.
How it works
There are two ways in. The REST API at https://api.vendo-ai.com/v1 takes and returns JSON, and every response is wrapped in {"data": …}. The MCP server at https://api.vendo-ai.com/mcp offers the same features as tools, so an AI assistant can work in your Vendo AI account for you.
Both use the same API key. You create it in the app under Settings, API & MCP and send it as Authorization: Bearer vnd_live_…. A key belongs to one workspace: if you pass the id of something from another workspace, you get a 404.
Anything that spends credits, contacts real people or deletes data happens in two steps. First you send a dry_run to see what will happen, then the same request with confirm. In MCP that is a preview_* tool followed by the real one. Slow operations answer with 202 and a job id you can poll. Billing is read-only, and payments, buying phone numbers and deleting the account stay in the app.
AI agents
Create an agent, scan a website, generate the script, pick a voice and avatar, then publish it as a website widget, Slide Presenter, Google Meet guest, Telegram bot, phone line or WhatsApp number.
Vendo SDR
Describe your company, let it research your ideal customer, run searches with 24 signal hunters, check every contact against your ICP and start email and LinkedIn outreach.
Campaigns
Multi-channel campaigns with previews, mailboxes, LinkedIn accounts, phone calls, AI conversation threads and live stats.
CRM and audiences
Leads, pipelines, documents, messages and the calendar, plus search across the people database, custom audiences and Audience Sets.
New to Vendo AI? Start with the product overview, the Vendo SDR page and pricing. Writing code? Go to the quick start.
Quick Start
- Get a key. In app.vendo-ai.com › Settings → API & MCP click Create my key and copy the
vnd_live_…value (shown once). Keys created there are full-access. - Call the API. Base URL
https://api.vendo-ai.com/v1, headerAuthorization: Bearer $VENDO_API_KEY. The three calls below list your agents, create a Vendo SDR search and read the contacts it finds. - Or connect an AI assistant to
https://api.vendo-ai.com/mcp: see MCP setup.
export VENDO_API_KEY="vnd_live_YOUR_KEY"
# 1. List your agents (AI sales agents / company profiles)
curl -s https://api.vendo-ai.com/v1/agents \
-H "Authorization: Bearer $VENDO_API_KEY"
# 2. Create a Vendo SDR search for one agent that already has company details.
# All 24 signal hunters are enabled by default; target_regions is required.
export AGENT_ID="paste-an-agent-id"
curl -s -X POST https://api.vendo-ai.com/v1/sdr/searches \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d "{\"agent_id\": \"$AGENT_ID\", \"name\": \"UK buyers\", \"target_regions\": [\"GB\"]}"
# → {"data": {"search": {"id": "…", "status": "active", …}, "activated": true, …}}
# 3. Read the contacts the hunters found (they arrive over minutes and hours)
export SEARCH_ID="paste-data.search.id"
curl -s "https://api.vendo-ai.com/v1/sdr/people?search_id=$SEARCH_ID&limit=20" \
-H "Authorization: Bearer $VENDO_API_KEY"// Node 18+ (built-in fetch). Save as quickstart.mjs and run: node quickstart.mjs
const BASE = "https://api.vendo-ai.com/v1";
const KEY = process.env.VENDO_API_KEY;
async function vendo(method, path, body) {
const res = await fetch(BASE + path, {
method,
headers: { Authorization: `Bearer ${KEY}`, "Content-Type": "application/json" },
body: body ? JSON.stringify(body) : undefined,
});
const json = await res.json();
if (!res.ok) throw new Error(`${res.status} ${json.code}: ${json.message}`);
return json.data;
}
// 1. List agents
const agents = await vendo("GET", "/agents");
console.log(agents.map((a) => `${a.id} ${a.name}`));
// 2. Create an SDR search (all 24 hunters by default; target_regions required)
const created = await vendo("POST", "/sdr/searches", {
agent_id: agents[0].id,
name: "UK buyers",
target_regions: ["GB"],
});
const searchId = created.search.id;
// 3. Read contacts found so far (poll again later — hunters run continuously)
const people = await vendo("GET", `/sdr/people?search_id=${searchId}&limit=20`);
console.log(people.items);# pip install requests ; export VENDO_API_KEY=vnd_live_…
import os
import requests
BASE = "https://api.vendo-ai.com/v1"
S = requests.Session()
S.headers["Authorization"] = f"Bearer {os.environ['VENDO_API_KEY']}"
def vendo(method, path, body=None, **params):
r = S.request(method, BASE + path, json=body, params=params or None, timeout=260)
if r.status_code >= 400:
e = r.json()
raise RuntimeError(f"{r.status_code} {e.get('code')}: {e.get('message')}")
return r.json()["data"]
# 1. List agents
agents = vendo("GET", "/agents")
for a in agents:
print(a["id"], a["name"])
# 2. Create an SDR search (all 24 hunters by default; target_regions required)
created = vendo("POST", "/sdr/searches", {
"agent_id": agents[0]["id"], "name": "UK buyers", "target_regions": ["GB"],
})
search_id = created["search"]["id"]
# 3. Read contacts found so far (poll again later)
people = vendo("GET", "/sdr/people", search_id=search_id, limit=20)
for p in people["items"]:
print(p["full_name"], p["title"], p["company_name"], p.get("email"))Creating a search is free; every usable contact a search delivers costs 5 credits (contacts the ICP check marks Not a fit are free). Use a vnd_test_… key to explore read-only first.
Authentication & scopes
API keys
Keys look like vnd_live_ + 64 hex characters (32 random bytes). Test keys use the prefix vnd_test_. Send the key on every request in either header:
Authorization: Bearer vnd_live_YOUR_KEY
# or
X-API-Key: vnd_live_YOUR_KEY- Full access by default. Keys created in Settings → API & MCP are full-access keys: they automatically receive every scope, including scopes added in later releases (for example the
crm:*andworkspace:*scopes). - Hashed storage. Only a SHA-256 hash is stored; the raw key is shown once.
- Instant revocation. Delete a key in Settings and it fails on the next request with
401 bad_api_key. - Optional IP allow-list. A key with an allow-list answers
403 ip_not_allowedfrom other addresses.
Sandbox (test) keys
vnd_test_… keys are a read-only sandbox: GET requests, preview routes and {"dry_run": true} calls work; any real write is refused with 403 sandbox_read_only. Nobody is contacted and no credits are spent. Switch to a live key for real writes.
Scopes
Each endpoint requires one scope (shown on every endpoint card). A key without it gets 403 missing_scope. campaigns:control (launch/pause/send) is deliberately separate from campaigns:write.
| Scope | Allows |
|---|---|
agents:read | Agents, channels, voices, presenter, Google Meet, Telegram, phone and WhatsApp reads, meetings calendar, previews, jobs |
agents:write | Create/update/delete agents, scans and script generation, knowledge, channels and their settings, test calls, meetings actions |
analytics:read | Campaign, agent and SDR analytics, live status, monitor, search stats, events and activity feeds |
audiences:read | People search preview, saved and custom audiences, Audience Sets, Vendo SDR searches, research, hunters and contacts |
audiences:write | Save/upload/enrich audiences, buy Audience Sets, create and edit SDR searches, run hunters, ICP QA, contact groups |
billing:read | Balance, subscription, limits, pricing, transactions, invoices and credit usage (there is no billing write scope) |
campaigns:control | Launch, pause, resume, archive; send now; reply in threads and LinkedIn; take over conversations (separate scope for safety) |
campaigns:read | Campaigns, emails, replies, threads, LinkedIn accounts and conversations, mailboxes, SDR outreach settings, planner |
campaigns:write | Create/update/delete campaigns, prepare emails, connect mailboxes and LinkedIn accounts, SDR outreach settings and content |
crm:read | CRM leads, pipelines and stages, documents, communications stream, calendar, notifications, settings, lookups |
crm:write | Create/import/update leads, move between stages and pipelines, reply and email, comments, documents, stages, calendar, settings |
workspace:read | Workspace and team, your profile and notification preferences, in-app notifications, CRM integrations, referral program, content accounts |
workspace:write | Invite and manage members, update profile and preferences, notifications, connect/disconnect integrations and content accounts, referral links |
There is intentionally no billing write scope: a leaked key can never change a plan, top up or move money.
Security model: workspace isolation
- Every key belongs to exactly one workspace; all calls run as that workspace and any caller-supplied workspace id is ignored.
- Every id in a request is verified against your workspace. An id from another workspace answers
404 not_found(never 403), so the API does not reveal whether another tenant's object exists. - Members' keys act with the member's own permissions inside the workspace (for example members see only the LinkedIn accounts they connected).
- All REST and MCP calls are audit-logged; MCP calls are tagged
X-Vendo-Via: mcp.
Conventions
Requests, responses and pagination
- JSON request bodies (
Content-Type: application/json) and JSON responses. Successful responses are wrapped:{"data": …}(a few add sibling fields such asnoteorsummary). CSV export routes (…/export.csv) return the raw file without the envelope. - Errors return an HTTP status and
{"code": "…", "message": "…"}. - Success codes: 201 only when a POST really creates something (an agent, channel, campaign, lead, stage, event, audience…); 200 for every other success, reads, PATCH, DELETE, previews,
generate-*, scans, sends and state actions (launch, pause, archive, run-now…), and for any POST sent with{"dry_run": true}(nothing is created); 202 when a long job was started and is still running (see Long-running jobs). Treat any 2xx as success. - A request that is missing a required field never returns 2xx with an
errorobject; it returns400 invalid_request. - Ids are UUIDs unless noted (Audience Sets use a slug for reads). Timestamps are ISO-8601 UTC.
- List endpoints that page accept
limitandoffset(see each endpoint's parameters; most caplimitat 100–200). Read the totals the endpoint returns (for exampletotal) to page further. - Many write routes validate an explicit allow-list of body fields; an unexpected field returns
400 forbidden_fieldinstead of being ignored. - Body size: up to 50 MB per request on
/v1; JSON bodies up to 10 MB.
Errors
{
"code": "missing_scope",
"message": "Required scope(s) not on key: agents:write"
}| HTTP | Code | Meaning |
|---|---|---|
| 400 | invalid_request | A parameter is missing or invalid; the message names it. |
| 400 | bad_request | Malformed request (for example both dry_run and confirm sent). |
| 400 | forbidden_field | The body carries a field that cannot be set on this route (allow-listed bodies). |
| 400 | invalid_parameter | A path id is not a valid UUID. |
| 400 | confirmation_required | Protected action called without dry_run or confirm. |
| 401 | missing_api_key | No Authorization: Bearer or X-API-Key header. |
| 401 | bad_api_key | The key is invalid, revoked or expired. |
| 402 | insufficient_credits | The credit balance is too low for this action. |
| 403 | missing_scope | The key lacks a required scope (message lists it). |
| 403 | sandbox_read_only | A test key (vnd_test_…) attempted a real write. |
| 403 | ip_not_allowed | The key has an IP allow-list and the client IP is not on it. |
| 404 | not_found | The resource does not exist or belongs to another workspace. |
| 409 | conflict | State conflict (for example a deep scan already running, a duplicate name). |
| 409 | idempotency_conflict | The Idempotency-Key was already used for a different request. |
| 413 | payload_too_large | Request body or file over the limit. |
| 429 | rate_limited | Over 60 requests/minute for this key; wait Retry-After seconds. |
| 429 | too_many_failed_auth | Too many failed authentication attempts from this IP; retry later. |
| 502 | internal_error | An upstream service failed; safe to retry reads, use Idempotency-Key for writes. |
| 504 | timeout | An upstream call timed out. |
Endpoint cards list their specific codes (for example 409 research_not_ready, 409 not_draft, 422 audience_required). Retry 429 after Retry-After; retry 5xx with backoff, using an Idempotency-Key on writes.
Rate limits
Default: 60 requests per minute per API key (sliding window). Over the limit you get 429 rate_limited.
| Header | Description |
|---|---|
X-RateLimit-Limit | Requests allowed per window (default 60) |
X-RateLimit-Remaining | Requests left in the current window |
Retry-After | Seconds until you may retry (sent with 429) |
X-Idempotency-Replay | true when an Idempotency-Key matched a stored response |
Over MCP, rate limiting is reported as a tool error (isError: true) inside a normal HTTP 200 JSON-RPC response, the transport call itself succeeded. Its first text line reads rate_limited, retry after N s and the JSON payload carries "code": "rate_limited" and retry_after_seconds; wait that long and call the tool again. Do not rely on an HTTP 429 from /mcp.
Repeated failed authentication from one IP is throttled separately (429 too_many_failed_auth). Some actions have product limits of their own (for example at most 5 new SDR company profiles per key per hour).
Protected actions: dry_run → confirm
Actions that delete data, spend credits or contact real people need two calls. {"dry_run": true} returns a preview (what would happen, counts, cost, blockers) and changes nothing; {"confirm": true} executes. Show the preview to a human before confirming. Sending neither flag returns 400 confirmation_required; sending both returns 400 bad_request. Test keys may always call dry_run.
# Step 1 — preview: nothing happens, you get a report
curl -s -X POST "https://api.vendo-ai.com/v1/campaigns/$CAMPAIGN_ID/launch" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"dry_run": true}'
# → {"data": {"dry_run": true, "would_do": "Launch campaign \"UK SaaS founders — Q4\"",
# "ready": true, "blockers": [], "recipient_count": 412, …}}
# Step 2 — execute after a human reviewed the preview
curl -s -X POST "https://api.vendo-ai.com/v1/campaigns/$CAMPAIGN_ID/launch" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"confirm": true}'
# Neither flag → 400 {"code": "confirmation_required", …}; both → 400 bad_requestAll 53 protected endpoints
DELETE /v1/agents/:id: Delete agent (requires confirm)DELETE /v1/agents/:id/connections/:cid: Remove a channelPOST /v1/agents/:id/deep-scan: Deep website scan (job)DELETE /v1/agents/:id/google-meet: Disconnect Google MeetDELETE /v1/agents/:id/narrator/leads/:leadId: Delete a presentation leadPOST /v1/agents/:id/phone/setup: Set up the Voice Call channelPOST /v1/agents/:id/phone/test-call: Test call (real phone)POST /v1/agents/:id/preview/linkedin-followup: Preview the LinkedIn follow-up messagePOST /v1/agents/:id/preview/linkedin-invite: Preview the LinkedIn invite textDELETE /v1/agents/:id/telegram: Disconnect the Telegram botPOST /v1/agents/:id/telegram/chats/:chatId/send: Send an operator messageDELETE /v1/audiences/custom/:id: Delete a custom audienceDELETE /v1/audiences/custom/:id/contacts/:contactId: Remove a contactPOST /v1/audiences/custom/:id/enrich: Enrich a custom audiencePOST /v1/audiences/sets/:id/buy: Buy an audience setPOST /v1/audiences/sets/:id/launch: Prepare a campaign audience from a setPOST /v1/campaigns/emails/:emailId/reply: Reply to a leadPOST /v1/campaigns/threads/:threadId/reply: Human reply in a conversationDELETE /v1/campaigns/:id: Delete campaign (requires confirm)POST /v1/campaigns/:id/archive: Archive a campaignPATCH /v1/campaigns/:id/discovery: Enable / change AI lead discoveryPOST /v1/campaigns/:id/emails/schedule-delivery: Schedule prepared emailsPOST /v1/campaigns/:id/emails/send-now: Send prepared emails nowPOST /v1/campaigns/:id/launch: Launch or resume a campaignPOST /v1/campaigns/:id/linkedin/activate: Activate LinkedIn on a campaignPOST /v1/campaigns/:id/phone/activate: Activate AI phone callsDELETE /v1/content/accounts/:accountId: Disconnect a publishing accountDELETE /v1/crm/branches/:id: Delete a pipelineDELETE /v1/crm/leads/:id: Soft-delete a lead (requires confirm)DELETE /v1/crm/leads/:id/documents/:docId: Delete a lead documentPOST /v1/crm/leads/:id/email: Send an email from the lead cardDELETE /v1/crm/pipeline-stages/:id: Delete a pipeline stageDELETE /v1/email/mailboxes/:mailboxId: Disconnect a mailboxDELETE /v1/integrations/:provider: Disconnect a CRM integrationDELETE /v1/linkedin/accounts/:id: Disconnect a LinkedIn accountPOST /v1/linkedin/accounts/:id/reconnect: Reconnect a LinkedIn accountPOST /v1/linkedin/conversations/:chatId/messages: Send a LinkedIn messagePOST /v1/meetings/:meetingId/cancel: Cancel a meeting for the agentPOST /v1/meetings/:meetingId/remove-agent: Remove the agent from a live callPOST /v1/sdr/people/bulk-delete: Remove several contactsDELETE /v1/sdr/people/:id: Remove a contactPATCH /v1/sdr/people/:id/group: Move a contact between ICP groupsPOST /v1/sdr/research: Start the New SDR wizard for a company websiteDELETE /v1/sdr/searches/:id: Delete an SDR process and all its dataDELETE /v1/sdr/searches/:id/outreach: Delete outreach campaign (paired dry_run/confirm)POST /v1/sdr/searches/:id/outreach/content/preview: Render a personalised message previewPOST /v1/sdr/searches/:id/outreach/launch: Launch SDR outreachPOST /v1/sdr/searches/:id/run-now: Run hunters nowPOST /v1/whatsapp/accounts/connect: Get a WhatsApp connect linkDELETE /v1/whatsapp/accounts/:accountId: Disconnect a WhatsApp accountPOST /v1/workspace/invitations: Invite a teammateDELETE /v1/workspace/members/:userId: Remove a teammatePATCH /v1/workspace/members/:userId/role: Promote / demote an admin
In MCP the same actions are split into a preview_* tool and an execute tool, see MCP safety.
Idempotency
For any POST / PATCH / DELETE you may send an Idempotency-Key header. Replaying the same request with the same key returns the stored result without running the side effect again.
curl -s -X POST https://api.vendo-ai.com/v1/campaigns \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Idempotency-Key: 6c1f3a2e-campaign-q4" \
-H "Content-Type: application/json" \
-d '{"name": "UK SaaS founders — Q4", "agentId": "…", "audienceId": "…"}'
# Retrying with the same key and body replays the stored response
# (header X-Idempotency-Replay: true) instead of creating a second campaign.- Same key + same request → stored response replayed, no side effect (
X-Idempotency-Replay: true). - Same key + different request →
409 idempotency_conflict. - TTL 24 hours; keys are namespaced per API key.
Long-running jobs and polling
Some actions take minutes. They never lose their result to a client timeout:
POST /v1/agents/:id/deep-scan(60–180 s) andPOST /v1/agents/:id/generate-script(60–90 s) wait up towait_seconds(0–240, default 200). If the job is still running you get HTTP 202 with{"data": {"job_id", "status": "running", "poll": "GET /v1/agents/{id}/jobs/{jobId}"}}. PollGET /v1/agents/:id/jobs/:jobIdevery 10–20 s (results are kept 1 hour);GET /v1/agents/:id/jobsrecovers a lost job id.wait_seconds: 0returns the job id immediately.- Vendo SDR research:
POST /v1/sdr/researchstarts it,GET /v1/sdr/research(byagent_id) reports the background company-profile build,GET /v1/sdr/research/:idis polled every 2–5 s untildone. - Custom-audience parsing and enrichment: poll
GET /v1/audiences/custom/:idandGET /v1/audiences/custom/:id/enrichment.
import time
def run_job(agent_id, path, body):
"""POST a job route (deep-scan / generate-script) and wait for the result."""
d = vendo("POST", f"/agents/{agent_id}/{path}", body) # waits up to wait_seconds (default 200)
while d.get("status") == "running": # HTTP 202: still running
time.sleep(15)
d = vendo("GET", f"/agents/{agent_id}/jobs/{d.get('job_id') or d['id']}")
if d["status"] == "error":
raise RuntimeError(d.get("error"))
return d # GET …/jobs/{id} returns the payload in d["result"]; a direct 200 spreads it into dFile uploads
File routes accept multipart/form-data or JSON with content_base64 (or image_base64 / file_base64) or a public file_url / image_url that the server fetches safely. Examples: POST /v1/audiences/custom/upload, POST /v1/crm/leads/:id/documents (field files, ≤10 files × 25 MB), POST /v1/agents/:id/documents (≤10 MB) and PATCH /v1/agents/:id/avatar (JPG/PNG/GIF ≤5 MB).
# multipart/form-data (file field "file")
curl -s -X POST https://api.vendo-ai.com/v1/audiences/custom/upload \
-H "Authorization: Bearer $VENDO_API_KEY" \
-F "file=@leads.csv" -F "name=Webinar leads"
# JSON with base64 content …
curl -s -X POST https://api.vendo-ai.com/v1/audiences/custom/upload \
-H "Authorization: Bearer $VENDO_API_KEY" -H "Content-Type: application/json" \
-d "{\"filename\": \"leads.csv\", \"content_base64\": \"$(base64 -w0 leads.csv)\"}"
# … or a public URL fetched server-side (SSRF-protected)
curl -s -X POST https://api.vendo-ai.com/v1/audiences/custom/upload \
-H "Authorization: Bearer $VENDO_API_KEY" -H "Content-Type: application/json" \
-d '{"filename": "leads.csv", "file_url": "https://example.com/exports/leads.csv"}'Webhooks and streams → polling
This release has no webhooks and no streaming endpoints. Everything the web app streams live is available by polling: SDR activity (GET /v1/sdr/searches/:id/outreach/activity-feed, GET /v1/sdr/searches/:id/events), campaign live status (GET /v1/campaigns/:id/live-status), notifications (GET /v1/notifications/unread-count). Poll every 10–60 s and stay within the rate limit.
Status endpoints
GET https://api.vendo-ai.com/v1 is the unauthenticated version index ({"api": "Vendo AI Public API", "version": "v1", …}); GET https://api.vendo-ai.com/health returns {"status": "ok", …}. They are not counted among the 362 endpoints.
REST API reference
Every /v1 endpoint of the release, grouped by product area. Each card shows the method, path, required scope and a one-line summary; open it for the description, parameters, a copy-paste curl request, a response example, errors and the matching MCP tools. Path placeholders such as $AGENT_ID are shell variables you set yourself. Badges: new added in Round-6, updated fixed or changed in Round-6, confirm uses the dry_run → confirm protocol.
362 endpoints in 9 areas.
Agents API 99
Create AI sales/support agents, build their knowledge and script, set the voice, and put them on channels: website widget and button, Slide Presenter, Google Meet, Telegram, phone (Voice Call) and WhatsApp.
Agent core & lifecycle 13
GET/v1/agentsList all agents
list agents in the workspace of this key. List all AI agents in the account. Read-only.
Parameters
No parameters.
Request
curl -s "https://api.vendo-ai.com/v1/agents" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: list_agents
POST/v1/agentsCreate an agent
Creates an agent like step 1 of the builder: name, direction (purpose), voice gender and a preset face. The agent starts in DRAFT and contacts nobody. Requires at least 1 credit on the balance (checked, not charged) and a free agent slot of the plan. Only the listed fields are accepted (metadata may only carry gender).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
name | body | string | yes | Display name (≤120) |
description | body | string | What the agent does (≤2000) | |
purpose | body | string | Direction; maps to agent_type one of: sales, support, info | |
agent_type | body | string | Alternative to purpose one of: FULL_AI_SALES_AGENT, AI_VOICE_SUPPORT_WIDGET, AI_INFO_NARRATOR | |
gender | body | string | Voice/face gender (default female) one of: male, female | |
avatar_url | body | string | Preset avatar-1 … avatar-20 (default: first preset of the gender) |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/agents" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Anna",
"description": "Answers questions about Acme CRM and books demos",
"purpose": "sales",
"gender": "female",
"avatar_url": "avatar-2"
}'Response
{
"data": {
"id": "3f6b2c1e-8a4d-4f0b-9c2e-7d1a5b6e9f10",
"name": "Anna",
"description": "Answers questions about Acme CRM and books demos",
"agent_type": "FULL_AI_SALES_AGENT",
"status": "DRAFT",
"avatar_url": "avatar-2",
"metadata": {
"creator": "universal",
"gender": "female"
},
"company_name": null,
"company_website": null,
"created_at": "2026-10-26T09:00:00.000Z"
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | forbidden_field | Body contains a field that is not settable here |
| 400 | invalid_request | Plan limit reached ("Your STARTER plan allows up to 2 agents…") or no credits on the balance |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: create_agent
GET/v1/agents/plan-limitsPlan capacity vs current agent count
How many agents can the user create (base plan + purchased extra slots) vs how many they have. Read-only.
Parameters
No parameters.
Request
curl -s "https://api.vendo-ai.com/v1/agents/plan-limits" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
GET/v1/agents/:idGet agent details
Fetch the full details of a single agent by id.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string | yes | The agent UUID |
Request
curl -s "https://api.vendo-ai.com/v1/agents/$AGENT_ID" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: get_agent
PATCH/v1/agents/:idUpdate agent basics (allow-listed)
Changes name, description, direction, gender or preset face. Company fields are accepted for compatibility and are written through the validated company update (read-modify-write). Any other field is rejected with forbidden_field, status, scripts, voice, slides and contact channels have their own routes.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Agent id |
name | body | string | ||
description | body | string | ||
purpose | body | string | one of: sales, support, info | |
agent_type | body | string | one of: FULL_AI_SALES_AGENT, AI_VOICE_SUPPORT_WIDGET, AI_INFO_NARRATOR | |
gender | body | string | one of: male, female | |
avatar_url | body | string | Preset avatar-1 … avatar-20 | |
company_name | body | string | Prefer PATCH /v1/agents/:id/company | |
company_website | body | string | ||
company_description | body | string |
Request
curl -s -X PATCH "https://api.vendo-ai.com/v1/agents/$AGENT_ID" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"purpose": "support", "gender": "male", "avatar_url": "avatar-4"}'Response
{
"data": {
"id": "3f6b2c1e-8a4d-4f0b-9c2e-7d1a5b6e9f10",
"name": "Anna",
"description": "Answers questions about Acme CRM and books demos",
"agent_type": "AI_VOICE_SUPPORT_WIDGET",
"status": "DRAFT",
"avatar_url": "avatar-4",
"metadata": {
"creator": "universal",
"gender": "male"
},
"company_name": null,
"company_website": null,
"created_at": "2026-10-26T09:00:00.000Z"
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | forbidden_field | Body contains a field that is not settable here |
| 404 | not_found | Agent does not exist or is not in your workspace |
| 400 | invalid_request | Nothing to update / invalid value |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: update_agent
DELETE/v1/agents/:idDelete agent (requires confirm)
destructive, requires confirm/dry_run. Permanently delete an agent. NOT reversible. Only call after the user has reviewed preview_delete_agent output and explicitly approved.
Protected: send {"dry_run": true} to preview, then {"confirm": true} to execute (protocol).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string | yes | |
dry_run | body | boolean | true = preview only (nothing happens) | |
confirm | body | boolean | true = execute. Send exactly one of dry_run / confirm |
Request
curl -s -X DELETE "https://api.vendo-ai.com/v1/agents/$AGENT_ID" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"dry_run": true}'Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
PATCH/v1/agents/:id/avatarUpload the agent's own photo
Sets a custom avatar from a public image URL (fetched SSRF-safely) or base64. JPG, PNG or GIF up to 5 MB; replaces the preset face and deletes the previous uploaded photo.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Agent id |
image_url | body | string | Public http(s) image URL | |
image_base64 | body | string | Base64 or data: URL (instead of image_url) |
Request
curl -s -X PATCH "https://api.vendo-ai.com/v1/agents/$AGENT_ID/avatar" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"image_url": "https://cdn.acme.com/team/anna.jpg"}'Response
{
"data": {
"id": "3f6b2c1e-8a4d-4f0b-9c2e-7d1a5b6e9f10",
"name": "Anna",
"description": "Answers questions about Acme CRM and books demos",
"agent_type": "FULL_AI_SALES_AGENT",
"status": "DRAFT",
"avatar_url": "/uploads/avatars/agent-4f1c9e0b7a2d3c5e6f8a9b0c1d2e3f40.jpg",
"metadata": {
"creator": "universal",
"gender": "female"
},
"company_name": null,
"company_website": null,
"created_at": "2026-10-26T09:00:00.000Z"
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | Agent does not exist or is not in your workspace |
| 400 | invalid_request | No image, unsupported type (WebP/SVG) or larger than 5 MB |
| 400 | forbidden_url | image_url points to a private network |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
PATCH/v1/agents/:id/companySet / update the company block: name, website, description and contact_channels, see the warning below
Required before generate_agent_script will work. Set the company the agent represents, its name, website, and what it sells.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string | yes | |
company_name | body | string | yes | |
company_website | body | string | yes | |
company_description | body | string | yes |
Request
curl -s -X PATCH "https://api.vendo-ai.com/v1/agents/$AGENT_ID/company" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"company_name": "<company_name>",
"company_website": "<company_website>",
"company_description": "<company_description>"
}'Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: set_agent_company
PATCH/v1/agents/:id/contact-channelsSet contact channels (merge)
#patch-v1-agents-id-contact-channels
Sets the contacts the agent hands to visitors (also used for interactive screen cards). Merge semantics: omitted keys keep their value, "" or null clears one; custom_links replaces the list. URLs must be http(s), email and phone are validated. Refreshes the card functions of the widget/presenter voice agents.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Agent id |
meeting_url | body | string | Booking link | |
presentation_url | body | string | Deck link | |
email | body | string | ||
phone | body | string | ||
whatsapp | body | string | ||
telegram | body | string | ||
custom_links | body | array | ≤10 {label (≤60), url, note? (≤200)}; null clears |
Request
curl -s -X PATCH "https://api.vendo-ai.com/v1/agents/$AGENT_ID/contact-channels" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"meeting_url": "https://calendly.com/acme/demo",
"custom_links": [
{
"label": "Pricing",
"url": "https://acme.com/pricing",
"note": "When asked about prices"
}
]
}'Response
{
"data": {
"contact_channels": {
"meeting_url": "https://calendly.com/acme/demo",
"email": "sales@acme.com",
"custom_links": [
{
"label": "Pricing",
"url": "https://acme.com/pricing",
"note": "When asked about prices"
}
]
}
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | forbidden_field | Body contains a field that is not settable here |
| 404 | not_found | Agent does not exist or is not in your workspace |
| 400 | invalid_request | Invalid URL / email / phone |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
POST/v1/agents/:id/duplicateClone an agent
Create a copy of an existing agent (new id, same config). Counts toward agent capacity.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string | yes |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/agents/$AGENT_ID/duplicate" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: duplicate_agent
PATCH/v1/agents/:id/integrationsUpdate CRM integration config
#patch-v1-agents-id-integrations
Legacy flags on the agent (zoom_enabled, google_meet_enabled, website_embed_enabled). Channels are managed with the connection tools; this does not connect anything. Free.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string | yes | Agent id (UUID), from list_agents |
zoom_enabled | body | boolean | ||
google_meet_enabled | body | boolean | ||
website_embed_enabled | body | boolean |
Request
curl -s -X PATCH "https://api.vendo-ai.com/v1/agents/$AGENT_ID/integrations" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
PATCH/v1/agents/:id/scriptManually overwrite script
Replace the script stages (rename, reorder, edit, add or remove stages) and optionally the tone, like the builder's script editor. Send the FULL list of stages you want; the full text is rebuilt from them. Get the current stages from get_agent (script_sections). Free.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string | yes | Agent id (UUID), from list_agents |
sections | body | object[] | yes | All stages in order min items 1 |
script_style | body | string | Tone; omit to keep the current one one of: PROFESSIONAL_FORMAL, FRIENDLY_CASUAL, TECHNICAL_EXPERT, CONSULTATIVE | |
email_script | body | string | Written-channel script (email/LinkedIn); omit to keep |
Request
curl -s -X PATCH "https://api.vendo-ai.com/v1/agents/$AGENT_ID/script" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"sections": []}'Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: update_agent_script
PATCH/v1/agents/:id/statusSet agent status
Sets the agent status (ACTIVE, PAUSED, DRAFT or ARCHIVED; case-insensitive). Fixed (2026-10-26): an unknown value answers 400 invalid_request (it used to reach the database and answer 500), extra body fields answer 400 forbidden_field, a foreign agent id answers 404.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | Agent id |
status | body | string | yes | New status one of: ACTIVE, PAUSED, DRAFT, ARCHIVED |
Request
curl -s -X PATCH "https://api.vendo-ai.com/v1/agents/$AGENT_ID/status" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"status": "PAUSED"}'Response
{
"data": {
"id": "a91d4f60-2c3b-4e7a-8f15-0b6c9d2e4a71",
"name": "Ava",
"status": "PAUSED",
"updated_at": "2026-10-26T14:02:11.000Z"
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | status is not one of ACTIVE, PAUSED, DRAFT, ARCHIVED |
| 400 | forbidden_field | Body has fields other than status |
| 404 | not_found | Agent does not exist or is not in your workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: set_agent_status
Builder: website scan, description, script 10
POST/v1/agents/page-descriptionsAI page descriptions without an agent
#post-v1-agents-page-descriptions
Generates short AI descriptions of website pages for a site that has no agent yet. Body: website_url and pages (the page list returned by POST /v1/agents/scan).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
website_url | body | string | yes | Site URL |
pages | body | object[] | yes | Pages from POST /v1/agents/scan |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/agents/page-descriptions" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
POST/v1/agents/scanQuick website scan
Lists a website's pages (path, title, description, keywords) before an agent exists. Fixed: the gateway now sends the field the backend reads (it always answered 400 "Website URL is required"). A missing website_url now answers 400 invalid_request (it used to answer 201 with an error object).
Credits: 2 credits
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
website_url | body | string | yes |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/agents/scan" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"website_url": "https://acme.com"}'Response
{
"data": {
"homepage": "https://acme.com",
"pages": [
{
"path": "/pricing",
"title": "Pricing",
"description": "",
"keywords": []
}
]
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | website_url missing or empty |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: scan_website_quick
POST/v1/agents/:id/deep-scanDeep website scan (job)
The builder's "Read my website": crawls the site and LinkedIn and extracts description, team, employee count, contacts, social links and pricing (60–180 s). Runs as a job: waits up to wait_seconds (default 200) and returns the result, or 202 with job_id, poll GET /v1/agents/:id/jobs/:jobId (kept 1 h) so a timeout never loses the paid result. persist=true (default) saves like the app: company_description replaced and save-scan-data applied (company + people records, blank contact channels filled).
Credits: 15 credits · Protected: send {"dry_run": true} to preview, then {"confirm": true} to execute (protocol).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Agent id |
dry_run | body | boolean | true = preview only (nothing happens) | |
confirm | body | boolean | true = execute. Send exactly one of dry_run / confirm | |
company_name | body | string | Default: agent.company_name | |
company_website | body | string | Default: agent.company_website | |
persist | body | boolean | Save the result onto the agent (default true) | |
auto_update | body | object | {enabled, interval: daily|weekly|biweekly|monthly|quarterly} automatic re-scan | |
wait_seconds | body | integer | 0–240, default 200; 0 = return the job id immediately |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/agents/$AGENT_ID/deep-scan" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"confirm": true,
"company_name": "Acme",
"company_website": "https://acme.com",
"persist": true
}'Response
Show the full response (53 lines)
{
"data": {
"job_id": "c2a7e4f1-6d3b-4a9e-8c5f-1e2d3c4b5a69",
"status": "done",
"final": {
"companyDescription": "Acme builds a CRM for small sales teams...",
"directors": [
{
"name": "John Smith",
"position": "CEO",
"linkedin": "https://linkedin.com/in/jsmith"
}
],
"employeeCount": "11-50",
"contacts": {
"addresses": [
"1 Main St, London"
],
"phones": [
"+44 20 7946 0000"
],
"emails": [
"hello@acme.com"
]
},
"pricing": "Starter $29/mo, Pro $79/mo",
"socialLinks": {
"linkedin": "https://linkedin.com/company/acme",
"facebook": "",
"instagram": "",
"twitter": ""
},
"siteStructure": []
},
"events": [
{
"step": "description",
"status": "done",
"data": {
"description": "..."
}
}
],
"persisted": {
"company": true,
"scan_data": {
"companyId": "a1b2c3d4-0000-4000-8000-000000000001",
"savedPeople": 1,
"totalDirectors": 1
}
}
}
}Errors
202, Still running after wait_seconds: {job_id, status:'running'}
| HTTP | Code | When |
|---|---|---|
| 400 | confirmation_required | Neither dry_run nor confirm was sent |
| 404 | not_found | Agent does not exist or is not in your workspace |
| 400 | invalid_request | Missing company name/website, insufficient credits (15 required) |
| 409 | conflict | A deep scan is already running for this agent |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
POST/v1/agents/:id/generate-descriptionDraft the company description
#post-v1-agents-id-generate-description
AI-writes "what the agent knows" from the company website without a full scan. Fixed: accepts snake_case (company_name, company_website, target_language) and falls back to the agent's saved company; ownership is checked. Not saved, pass it to PATCH /v1/agents/:id/company.
Credits: 1 credit
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Agent id |
company_name | body | string | Default: agent.company_name | |
company_website | body | string | Default: agent.company_website | |
target_language | body | string | e.g. Ukrainian |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/agents/$AGENT_ID/generate-description" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"company_name": "Acme", "company_website": "acme.com"}'Response
{
"data": {
"description": "Acme builds a CRM for small sales teams..."
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | Agent does not exist or is not in your workspace |
| 400 | invalid_request | Company name/website missing or insufficient credits |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
POST/v1/agents/:id/generate-page-descriptionsAI page descriptions for an agent
#post-v1-agents-id-generate-page-descriptions
Write AI descriptions + keywords for website pages (output of scan_agent_website). Costs 1 credit per 5 pages. Not saved.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string | yes | Agent id (UUID), from list_agents |
pages | body | object[] | yes | min items 1 |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/agents/$AGENT_ID/generate-page-descriptions" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"pages": []}'Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
POST/v1/agents/:id/generate-responseGenerate a single AI reply for a given inbound message, using the agent's script and knowledge base. Useful for previewing how the agent would answer before enabling a channel
#post-v1-agents-id-generate-response
Send a message to the agent and get its reply using its current script, a text test, nobody is contacted. Needs a generated script. Use history for a multi-turn test.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string | yes | Agent id (UUID), from list_agents |
message | body | string | yes | What the visitor says |
history | body | object[] | Earlier turns, oldest first |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/agents/$AGENT_ID/generate-response" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"message": "<message>"}'Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: test_agent_reply
POST/v1/agents/:id/generate-scriptGenerate the script (job)
#post-v1-agents-id-generate-script
Generates the script (stages, full text, email/LinkedIn script) in the chosen tone for the agent's direction and saves it on the agent (overwrites). Takes 60–90 s; runs as a job with wait_seconds like deep-scan, so a client timeout never loses the result. Requires company_name and company_description.
Credits: 1 credit
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Agent id |
script_style | body | string | yes | Tone one of: PROFESSIONAL_FORMAL, FRIENDLY_CASUAL, TECHNICAL_EXPERT, CONSULTATIVE |
purpose | body | string | Default: from agent_type one of: sales, support, info | |
wait_seconds | body | integer | 0–240, default 200 |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/agents/$AGENT_ID/generate-script" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"script_style": "FRIENDLY_CASUAL", "purpose": "sales"}'Response
{
"data": {
"job_id": "c2a7e4f1-6d3b-4a9e-8c5f-1e2d3c4b5a69",
"status": "done",
"sections": [
{
"id": "greeting",
"title": "Greeting",
"content": "Hi! I'm Anna from Acme..."
}
],
"fullText": "[Greeting]\nHi! I'm Anna from Acme...",
"detectedLanguage": "English",
"emailScript": "Subject: ..."
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | Agent does not exist or is not in your workspace |
| 400 | invalid_request | Company info missing or insufficient credits |
| 409 | conflict | A script job is already running |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
POST/v1/agents/:id/generate-support-scriptGenerate support script
#post-v1-agents-id-generate-support-script
Legacy support-script generator of the old text-widget creators. Prefer generate_agent_script with purpose "support". Needs company name + description. Costs 1 credit.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string | yes | Agent id (UUID), from list_agents |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/agents/$AGENT_ID/generate-support-script" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
POST/v1/agents/:id/save-scan-dataPersist scan output onto the agent
#post-v1-agents-id-save-scan-data
Save company facts (team, contacts, social links, pricing, employee count, auto re-scan) onto the agent, what the app does after a scan. Side effects: stores them in the agent, creates/updates the company and people records, fills BLANK contact channels (never overwrites manual ones). Not needed after deep_scan_agent with persist=true. Free.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string | yes | Agent id (UUID), from list_agents |
company_name | body | string | yes | |
company_website | body | string | yes | |
directors | body | object[] | Team / management | |
contacts | body | object | ||
social_links | body | object | ||
pricing | body | string | ||
employee_count | body | number | string | ||
auto_update | body | object |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/agents/$AGENT_ID/save-scan-data" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"company_name": "<company_name>", "company_website": "<company_website>"}'Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: save_scan_data
POST/v1/agents/:id/scan-websiteQuick site scan attached to an agent
#post-v1-agents-id-scan-website
List the pages of a website (path, title, description, keywords) for this agent, used by the page-navigation feature. Costs 2 credits. Read-only for the agent (nothing saved).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string | yes | Agent id (UUID), from list_agents |
website_url | body | string | yes |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/agents/$AGENT_ID/scan-website" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"website_url": "<website_url>"}'Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: scan_agent_website
Long-running jobs 2
GET/v1/agents/:id/jobsList recent jobs
Deep scans and script generations of this agent started through the API in the last hour (without results). Use it to recover a lost job id. Jobs live in the gateway process memory and are lost on a restart. A foreign or deleted agent id now answers 404 (it used to return an empty list).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Agent id |
Request
curl -s "https://api.vendo-ai.com/v1/agents/$AGENT_ID/jobs" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": [
{
"id": "c2a7e4f1-6d3b-4a9e-8c5f-1e2d3c4b5a69",
"kind": "deep_scan",
"agent_id": "3f6b2c1e-8a4d-4f0b-9c2e-7d1a5b6e9f10",
"status": "running",
"started_at": "2026-10-26T10:00:00.000Z",
"progress": {
"description": "done",
"management": "scanning"
}
}
]
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | Agent does not exist or belongs to another workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: list_agent_jobs
GET/v1/agents/:id/jobs/:jobIdGet a job result
Status (running / done / error), per-step progress and the result of a deep scan or script generation job. Poll every 10–20 s while running.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Agent id |
jobId | path | string (uuid) | yes | From deep-scan / generate-script |
Request
curl -s "https://api.vendo-ai.com/v1/agents/$AGENT_ID/jobs/$JOB_ID" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Show the full response (53 lines)
{
"data": {
"id": "c2a7e4f1-6d3b-4a9e-8c5f-1e2d3c4b5a69",
"kind": "deep_scan",
"agent_id": "3f6b2c1e-8a4d-4f0b-9c2e-7d1a5b6e9f10",
"status": "done",
"started_at": "2026-10-26T10:00:00.000Z",
"finished_at": "2026-10-26T10:02:21.000Z",
"progress": {
"description": "done",
"management": "done",
"employees": "done",
"contacts": "done",
"pricing": "done",
"complete": "done"
},
"result": {
"final": {
"companyDescription": "Acme builds a CRM for small sales teams...",
"directors": [
{
"name": "John Smith",
"position": "CEO",
"linkedin": "https://linkedin.com/in/jsmith"
}
],
"employeeCount": "11-50",
"contacts": {
"addresses": [
"1 Main St, London"
],
"phones": [
"+44 20 7946 0000"
],
"emails": [
"hello@acme.com"
]
},
"pricing": "Starter $29/mo, Pro $79/mo",
"socialLinks": {
"linkedin": "https://linkedin.com/company/acme",
"facebook": "",
"instagram": "",
"twitter": ""
},
"siteStructure": []
},
"persisted": {
"company": true
}
}
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | Job does not exist or is not in your workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: get_agent_job
Knowledge 3
POST/v1/agents/:id/documentsUpload a knowledge file
Attaches a material (PDF, DOC, DOCX, TXT, XLS, XLSX, CSV, PPT, PPTX, KEY; ≤10 MB) from a public URL or base64, like the builder's "Upload materials". Used as knowledge the next time the script is generated. Free.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Agent id |
filename | body | string | yes | Name with a supported extension |
file_url | body | string | Public http(s) URL | |
file_base64 | body | string | Base64 content (instead of file_url) |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/agents/$AGENT_ID/documents" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"filename": "price-list.pdf", "file_url": "https://acme.com/files/price-list.pdf"}'Response
{
"data": {
"url": "/uploads/documents/doc-8e1f0c2b9a7d4e3f6a5b8c9d0e1f2a3b.pdf",
"filename": "price-list.pdf"
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | Agent does not exist or is not in your workspace |
| 400 | invalid_request | Unsupported extension, empty file or larger than 10 MB |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: add_agent_document
DELETE/v1/agents/:id/documentsRemove a knowledge file
#delete-v1-agents-id-documents
Removes an uploaded material from the agent. Pass url (preferred) or filename. Fixed (2026-10-26): document urls returned by GET /v1/agents/:id carry a signing query (?md5=…&expires=…) that changes on every read; it is ignored when matching, and the stored path is what is removed, the url from any earlier GET works.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Agent id |
url | body|query | string | documents[].url from GET /v1/agents/:id | |
filename | body|query | string | documents[].filename |
Request
curl -s -X DELETE "https://api.vendo-ai.com/v1/agents/$AGENT_ID/documents" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "/uploads/documents/doc-8e1f0c2b9a7d4e3f6a5b8c9d0e1f2a3b.pdf?md5=Qm3x…&expires=1793102400"
}'Response
{
"data": {
"removed": "/uploads/documents/doc-8e1f0c2b9a7d4e3f6a5b8c9d0e1f2a3b.pdf",
"documents": []
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | Agent or document does not exist or is not in your workspace |
| 400 | invalid_request | Neither url nor filename |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
POST/v1/agents/:id/generate-block-from-urlKnowledge block from a web page
#post-v1-agents-id-generate-block-from-url
Reads a public web page and returns a knowledge text block about it. Not saved, append it to company_description if wanted. Private/internal addresses are refused.
Credits: 1 credit
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Agent id |
url | body | string | yes | Public http(s) page |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/agents/$AGENT_ID/generate-block-from-url" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://acme.com/features"}'Response
{
"data": {
"content": "Acme CRM features: pipeline boards, email sync, AI lead scoring..."
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | Agent does not exist or is not in your workspace |
| 400 | invalid_request | Missing URL or insufficient credits |
| 400 | forbidden_url | URL resolves to a private network |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
Voice 4
GET/v1/agents/voices/catalogVoice catalogue (Voice v2)
All voices offered on the widget, presentation and Telegram voice pages, with engine, expressive and Ukrainian support and the recommended flag.
Parameters
No parameters.
Request
curl -s "https://api.vendo-ai.com/v1/agents/voices/catalog" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": [
{
"voice_id": "retell-Grace",
"voice_name": "Grace",
"gender": "female",
"accent": "American",
"age": "Young",
"preview_audio_url": "https://retell-utils-public.s3.amazonaws.com/grace.mp3",
"provider": "retell",
"engine": "platform",
"expressive": true,
"ukrainian": false,
"recommended": true
}
]
}Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: list_voice_catalog
GET/v1/agents/voices/retellList available Retell voices
Catalogue of voices that can be assigned via set_agent_voice. Read-only.
Parameters
No parameters.
Request
curl -s "https://api.vendo-ai.com/v1/agents/voices/retell" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: list_retell_voices
PATCH/v1/agents/:id/voiceUpdate raw voice settings (legacy)
Legacy pass-through: the JSON body is forwarded unchanged to the agent's stored voice settings. Prefer PATCH /v1/agents/:id/widget-voice, which validates the fields, merges them and provisions the voice at the provider.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Agent id |
(body) | body | object | yes | Raw voice settings object, forwarded as-is |
Request
curl -s -X PATCH "https://api.vendo-ai.com/v1/agents/$AGENT_ID/voice" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
PATCH/v1/agents/:id/widget-voiceSet the website-widget voice
#patch-v1-agents-id-widget-voice
Exactly what the widget settings page saves: voice, pace, emotion, languages, rules, sound, behaviour, model and screen cards. Merge over the saved Voice v2 settings (defaults = the app's). Sends retell_voice_id, so the widget voice agent is created/updated at the provider and voice really works (up to ~1 min). Use PATCH /v1/agents/:id/voice only for raw backend fields.
Credits: free (widget voice calls 14 credits/min)
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Agent id |
voice_id | body | string | Voice from GET /v1/agents/voices/catalog (required the first time) | |
speed | body | number | 0.7–1.3, 1 = normal | |
emotion | body | string | Delivery one of: neutral, expressive, calm, dynamic | |
languages | body | string[] | Retell locales (xx-YY, ≤20). More than one = multilingual | |
rules | body | string | Communication rules (≤8000 chars). Default text is generated when none is saved | |
ambient | body | string | Background sound one of: "", coffee-shop, call-center, convention-hall | |
ambient_sound_volume | body | number | 0–2 | |
backchannel | body | boolean | Short acknowledgements while the person talks | |
backchannel_frequency | body | number | 0–1 | |
end_call_s | body | number | Hang up after N s of silence, 10–120 | |
volume | body | number | 0–2 | |
responsiveness | body | number | 0–1 | |
interruption_sensitivity | body | number | 0–1 | |
dynamic_voice_speed | body | boolean | ||
dynamic_responsiveness | body | boolean | ||
expressive_mode | body | boolean | Ignored for voices that are not expressive in the catalogue | |
denoising_mode | body | string | one of: noise-cancellation, noise-and-background-speech-cancellation | |
reminder_seconds | body | number | Nudge a silent person after N s; 0 = off | |
handbook | body | object | Booleans: conversational_personality, natural_filler_words, high_empathy, echo_verification, speech_normalization, ai_disclosure, scope_boundaries (merged) | |
llm | body | string | Model one of: fast, balanced, smart | |
screen_cards | body | boolean | Interactive cards on screen during voice calls |
Request
curl -s -X PATCH "https://api.vendo-ai.com/v1/agents/$AGENT_ID/widget-voice" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"voice_id": "retell-Grace",
"speed": 1.05,
"emotion": "expressive",
"languages": [
"en-US",
"uk-UA"
],
"llm": "balanced",
"screen_cards": true
}'Response
{
"data": {
"voice_ready": true,
"voice_id": "retell-Grace",
"language": "multi",
"communication_rules": "You are Anna, a sales representative at Acme...",
"voice_v2": {
"languages": [
"en-US",
"uk-UA"
],
"volume": 1,
"responsiveness": 0.8,
"llm": "balanced",
"screen_cards": true,
"emotion": "expressive",
"saved_at": "2026-10-26T10:12:03.000Z"
}
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | forbidden_field | Body contains a field that is not settable here |
| 404 | not_found | Agent does not exist or is not in your workspace |
| 400 | invalid_request | voice_id missing/unknown or invalid value |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: set_agent_voice
Channels (connections) 5
GET/v1/agents/:id/connectionsList all connections
See every channel attached to this agent: widgets, telegram, phone, presentations, etc.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string | yes |
Request
curl -s "https://api.vendo-ai.com/v1/agents/$AGENT_ID/connections" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
POST/v1/agents/:id/connectionsAdd a channel
#post-v1-agents-id-connections
Adds a channel. WEBSITE_WIDGET (website_url required) starts from the app defaults (Modern design) plus your widget_config; a second widget for the same URL updates the existing one. WEBSITE_BUTTON and PRESENTATION_WIDGET (= publish the Slide Presenter) need only a name/URL. GOOGLE_MEET gets the agent's own invite address. Fields are allow-listed and validated; TELEGRAM/WHATSAPP have their own routes.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Agent id |
type | body | string | yes | one of: WEBSITE_WIDGET, WEBSITE_BUTTON, PRESENTATION_WIDGET, GOOGLE_MEET, PHONE_CALL |
name | body | string | ||
website_url | body | string | Required for WEBSITE_WIDGET | |
widget_config | body | object | WEBSITE_WIDGET only. design classic|modern, theme{glass, glassOpacity, blur, headerStyle gradient|solid, gradient[2-3 hex], gradientAngle, accentGradient, openAnimation genie|spring|scale|slide|fade, voiceVisual portrait|liquid|waves}, position, colours (primaryColor, backgroundColor, textColor, buttonColor, buttonTextColor, headerColor, headerTextColor, userBubbleColor, userBubbleTextColor, agentBubbleColor, agentBubbleTextColor), borderRadius, logoVariant white|blue|black, launcherShape round|square, launcher_image_url | launcher_image_base64 (≤~110 KB), launcherImage "" (remove), collectName/Email/Phone, teaser, teaserDelay 3-30, autoOpen, autoOpenDelay, autoOpenSound, autoOpenMessage, replySound, unreadBadge, navigationEnabled, navigationMode auto|ask, continueAfterNavigate, texts{welcomeMessage, welcomeFollowUp, contact-form wording; ""/null = default} | |
google_meet_config | body | object | GOOGLE_MEET only: auto_join, join_before_minutes 0-30, greeting_message, language, meeting_mode voice_only|presentation (needs slides). agent_email is set by the platform | |
phone_call_config | body | object | PHONE_CALL only: call_script, greeting_message, call_objective, max_call_duration_seconds, post_call_actions. Number/voice/inbound → POST /v1/agents/:id/phone/setup |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/agents/$AGENT_ID/connections" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "WEBSITE_WIDGET",
"website_url": "acme.com",
"widget_config": {
"teaser": true,
"collectName": true,
"collectEmail": true,
"texts": {
"welcomeMessage": "Hi! I'\''m Anna from Acme."
}
}
}'Response
Show the full response (40 lines)
{
"data": {
"id": "b7e2d9a4-1c3f-4e8a-9d6b-2f5c8a1e4d73",
"agent_id": "3f6b2c1e-8a4d-4f0b-9c2e-7d1a5b6e9f10",
"type": "WEBSITE_WIDGET",
"status": "ACTIVE",
"name": "acme.com",
"website_url": "https://acme.com",
"widget_config": {
"design": "modern",
"position": "bottom-right",
"primaryColor": "#3B82F6",
"teaser": true,
"teaserDelay": 5,
"autoOpenMessage": "Hi! How can I help you today?",
"collectName": true,
"collectEmail": true,
"theme": {
"glass": true,
"glassOpacity": 0.78,
"blur": 24,
"headerStyle": "gradient",
"gradient": [
"#2563EB",
"#7C3AED"
],
"gradientAngle": 135,
"accentGradient": true,
"openAnimation": "genie",
"voiceVisual": "portrait"
},
"texts": {
"welcomeMessage": "Hi! I'm Anna from Acme."
},
"launcherImage": ""
},
"total_conversations": 0,
"created_at": "2026-10-26T09:05:00.000Z"
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | forbidden_field | Body contains a field that is not settable here |
| 404 | not_found | Agent does not exist or is not in your workspace |
| 400 | invalid_request | Invalid type/value, launcher image too large |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
GET/v1/agents/:id/connections/:cidGet a channel
#get-v1-agents-id-connections-cid
One channel with its settings. Changed: the embedded agent object and voice-provider ids are no longer returned (voice_agent_ready / inbound_agent_ready flags instead). A deleted channel answers 404 (deleted channels used to stay addressable).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Agent id |
cid | path | string (uuid) | yes | Channel (connection) id |
Request
curl -s "https://api.vendo-ai.com/v1/agents/$AGENT_ID/connections/$CONNECTION_ID" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Show the full response (40 lines)
{
"data": {
"id": "b7e2d9a4-1c3f-4e8a-9d6b-2f5c8a1e4d73",
"agent_id": "3f6b2c1e-8a4d-4f0b-9c2e-7d1a5b6e9f10",
"type": "WEBSITE_WIDGET",
"status": "ACTIVE",
"name": "acme.com",
"website_url": "https://acme.com",
"widget_config": {
"design": "modern",
"position": "bottom-right",
"primaryColor": "#3B82F6",
"teaser": true,
"teaserDelay": 5,
"autoOpenMessage": "Hi! How can I help you today?",
"collectName": true,
"collectEmail": true,
"theme": {
"glass": true,
"glassOpacity": 0.78,
"blur": 24,
"headerStyle": "gradient",
"gradient": [
"#2563EB",
"#7C3AED"
],
"gradientAngle": 135,
"accentGradient": true,
"openAnimation": "genie",
"voiceVisual": "portrait"
},
"texts": {
"welcomeMessage": "Hi! I'm Anna from Acme."
},
"launcherImage": ""
},
"total_conversations": 0,
"created_at": "2026-10-26T09:05:00.000Z"
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | Agent or channel does not exist, belongs to another workspace, or the channel was deleted |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: get_agent_connection
PATCH/v1/agents/:id/connections/:cidChange a channel
#patch-v1-agents-id-connections-cid
Changes name, website_url or the type's config. Widget settings are allow-listed and validated; texts and theme are merged with the stored ones (the backend replaces nested objects whole), launcher_image_url is converted to the stored picture. Channel status cannot be changed here. A deleted channel answers 404 (deleted channels used to stay addressable).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Agent id |
cid | path | string (uuid) | yes | Channel (connection) id |
name | body | string | ||
website_url | body | string | ||
widget_config | body | object | WEBSITE_WIDGET only. design classic|modern, theme{glass, glassOpacity, blur, headerStyle gradient|solid, gradient[2-3 hex], gradientAngle, accentGradient, openAnimation genie|spring|scale|slide|fade, voiceVisual portrait|liquid|waves}, position, colours (primaryColor, backgroundColor, textColor, buttonColor, buttonTextColor, headerColor, headerTextColor, userBubbleColor, userBubbleTextColor, agentBubbleColor, agentBubbleTextColor), borderRadius, logoVariant white|blue|black, launcherShape round|square, launcher_image_url | launcher_image_base64 (≤~110 KB), launcherImage "" (remove), collectName/Email/Phone, teaser, teaserDelay 3-30, autoOpen, autoOpenDelay, autoOpenSound, autoOpenMessage, replySound, unreadBadge, navigationEnabled, navigationMode auto|ask, continueAfterNavigate, texts{welcomeMessage, welcomeFollowUp, contact-form wording; ""/null = default} | |
google_meet_config | body | object | GOOGLE_MEET only: auto_join, join_before_minutes 0-30, greeting_message, language, meeting_mode voice_only|presentation (needs slides). agent_email is set by the platform | |
phone_call_config | body | object | PHONE_CALL only: call_script, greeting_message, call_objective, max_call_duration_seconds, post_call_actions. Number/voice/inbound → POST /v1/agents/:id/phone/setup |
Request
curl -s -X PATCH "https://api.vendo-ai.com/v1/agents/$AGENT_ID/connections/$CONNECTION_ID" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"widget_config": {
"design": "modern",
"theme": {
"voiceVisual": "liquid"
},
"launcher_image_url": "https://cdn.acme.com/anna-160.png",
"texts": {
"contactTitle": "Before we chat"
}
}
}'Response
Show the full response (41 lines)
{
"data": {
"id": "b7e2d9a4-1c3f-4e8a-9d6b-2f5c8a1e4d73",
"agent_id": "3f6b2c1e-8a4d-4f0b-9c2e-7d1a5b6e9f10",
"type": "WEBSITE_WIDGET",
"status": "ACTIVE",
"name": "acme.com",
"website_url": "https://acme.com",
"widget_config": {
"design": "modern",
"position": "bottom-right",
"primaryColor": "#3B82F6",
"teaser": true,
"teaserDelay": 5,
"autoOpenMessage": "Hi! How can I help you today?",
"collectName": true,
"collectEmail": true,
"theme": {
"glass": true,
"glassOpacity": 0.78,
"blur": 24,
"headerStyle": "gradient",
"gradient": [
"#2563EB",
"#7C3AED"
],
"gradientAngle": 135,
"accentGradient": true,
"openAnimation": "genie",
"voiceVisual": "portrait"
},
"texts": {
"welcomeMessage": "Hi! I'm Anna from Acme.",
"contactTitle": "Before we chat"
},
"launcherImage": "data:image/png;base64,iVBORw0KGgo..."
},
"total_conversations": 0,
"created_at": "2026-10-26T09:05:00.000Z"
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | forbidden_field | Body contains a field that is not settable here |
| 400 | invalid_request | Config not valid for this channel type / invalid value |
| 404 | not_found | Agent or channel does not exist, belongs to another workspace, or the channel was deleted |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
DELETE/v1/agents/:id/connections/:cidRemove a channel
#delete-v1-agents-id-connections-cid
Soft-deletes a channel (no restore route exists). Changed: now follows the destructive protocol, dry_run shows the channel and the effect, confirm removes it (breaking for callers that sent no flag). A deleted channel answers 404 (deleted channels used to stay addressable).
Protected: send {"dry_run": true} to preview, then {"confirm": true} to execute (protocol).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Agent id |
cid | path | string (uuid) | yes | Channel (connection) id |
dry_run | body | boolean | true = preview only (nothing happens) | |
confirm | body | boolean | true = execute. Send exactly one of dry_run / confirm |
Request
curl -s -X DELETE "https://api.vendo-ai.com/v1/agents/$AGENT_ID/connections/$CONNECTION_ID" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"dry_run": true}'Response
{
"data": {
"dry_run": true,
"would_do": "Remove channel \"acme.com\" (WEBSITE_WIDGET)",
"effect": "The widget embed code on the site stops loading this channel.",
"connection": {
"id": "b7e2d9a4-1c3f-4e8a-9d6b-2f5c8a1e4d73",
"type": "WEBSITE_WIDGET",
"name": "acme.com",
"website_url": "https://acme.com"
},
"reversible": false
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | confirmation_required | Neither dry_run nor confirm was sent |
| 404 | not_found | Agent or channel does not exist, belongs to another workspace, or the channel was deleted |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: preview_delete_agent_connection, delete_agent_connection
Website widget & button 3
GET/v1/agents/:id/connections/:cid/embed-codeEmbed code of a channel
#get-v1-agents-id-connections-cid-embed-code
The snippet the app copies: website widget (window.VendoAI + embed.js; settings load from the server), website button (styled by the query parameters, not stored server-side) or Slide Presenter (snippet + direct link). Replaces the legacy GET /v1/agents/:id/widget/embed-code. A deleted channel answers 404 (deleted channels used to stay addressable).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Agent id |
cid | path | string (uuid) | yes | Channel (connection) id |
text | query | string | Button label | |
background_color | query | hex | ||
text_color | query | hex | ||
border_radius | query | integer | 0–50 | |
size | query | string | one of: small, medium, large | |
modal_primary_color | query | hex |
Request
curl -s "https://api.vendo-ai.com/v1/agents/$AGENT_ID/connections/$CONNECTION_ID/embed-code" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"type": "WEBSITE_WIDGET",
"embed_code": "<!-- Vendo AI Widget -->\n<script>\n window.VendoAI = { agentId: '...', connectionId: '...' };\n</script>\n<script src=\"https://app.vendo-ai.com/widget/embed.js\" async></script>",
"note": "Widget settings load from the server; later changes apply without touching the site."
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | Channel type has no embed code |
| 404 | not_found | Agent or channel does not exist, belongs to another workspace, or the channel was deleted |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
POST/v1/agents/:id/connections/:cid/test-pageCreate a widget test page
#post-v1-agents-id-connections-cid-test-page
Creates (or returns, idempotent) a private, noindex test page with this website widget installed. Only the agent's creator may do this (a teammate's key gets 400).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Agent id |
cid | path | string (uuid) | yes | Channel (connection) id |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/agents/$AGENT_ID/connections/$CONNECTION_ID/test-page" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{}'Response
{
"data": {
"url": "https://report.vendo-ai.com/widget-test/acme-7f3a2c"
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | Channel does not exist or is not in your workspace |
| 400 | invalid_request | Not a website widget / you do not own this agent |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
GET/v1/agents/:id/widget/embed-codeEmbed code for the website widget (legacy)
#get-v1-agents-id-widget-embed-code
Legacy route kept for compatibility. Prefer GET /v1/agents/:id/connections/:cid/embed-code, which returns the snippet of a specific channel (widget, button or Slide Presenter).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string | yes |
Request
curl -s "https://api.vendo-ai.com/v1/agents/$AGENT_ID/widget/embed-code" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
Slide Presenter 14
POST/v1/agents/:id/generate-narrator-scriptWrite a slide's presenter script
#post-v1-agents-id-generate-narrator-script
AI-writes what the presenter says on one slide. Fixed: accepts snake_case, an image by URL (fetched by the gateway, sent to the vision model) and runs with a 200 s timeout. Not saved, put it into narrator_script with POST /v1/agents/:id/presentation/slides.
Credits: 1 credit (2 with image)
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Agent id |
slide_number | body | integer | yes | |
total_slides | body | integer | yes | |
extracted_text | body | string | Text on the slide | |
previous_script | body | string | ||
next_slide_text | body | string | ||
image_url | body | string | Slide image (vision, 2 credits) | |
image_base64 | body | string | Instead of image_url |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/agents/$AGENT_ID/generate-narrator-script" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"slide_number": 2,
"total_slides": 8,
"extracted_text": "Why teams switch to Acme",
"image_url": "https://cdn.acme.com/deck/2.png"
}'Response
{
"data": {
"script": "Here is why teams switch to Acme: ..."
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | Agent does not exist or is not in your workspace |
| 400 | invalid_request | Insufficient credits / bad image |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
POST/v1/agents/:id/generate-transition-phrasesSlide transition narration
#post-v1-agents-id-generate-transition-phrases
Generate the phrases the Slide Presenter uses to move between slides, in the presentation's language, and save them on the agent. Needs saved slides (set_presentation_slides).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string | yes | Agent id (UUID), from list_agents |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/agents/$AGENT_ID/generate-transition-phrases" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
GET/v1/agents/:id/narrator/analyticsPresentation analytics
#get-v1-agents-id-narrator-analytics
view=summary (default): views, presentations started, watch time, unique viewers, average duration, completion rate, recent sessions and per channel. view=period with date_from/date_to: widget loads, starts, watch minutes, unique viewers and credits. Fixed: ownership is checked and dates are forwarded.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Agent id |
view | query | string | one of: summary, period | |
date_from | query | date | ||
date_to | query | date |
Request
curl -s "https://api.vendo-ai.com/v1/agents/$AGENT_ID/narrator/analytics" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"view": "summary",
"widgetViews": 412,
"presentationsStarted": 96,
"totalWatchTimeSeconds": 18230,
"uniqueViewers": 81,
"avgDurationSeconds": 190,
"completionRate": 0.34,
"recentPresentations": [
{
"sessionId": "s_1",
"durationSeconds": 240,
"startedAt": "2026-10-25T16:02:00.000Z"
}
],
"byConnection": [
{
"connectionId": "b7e2d9a4-1c3f-4e8a-9d6b-2f5c8a1e4d73",
"connectionName": "acme.com",
"views": 300,
"plays": 70
}
]
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | Agent does not exist or is not in your workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
GET/v1/agents/:id/narrator/leadsList presentation leads
#get-v1-agents-id-narrator-leads
Leads captured by the presentation call-to-action, newest first. Fixed: status, limit and offset are forwarded.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Agent id |
status | query | string | one of: new, contacted, converted, spam | |
limit | query | integer | 1–500 | |
offset | query | integer |
Request
curl -s "https://api.vendo-ai.com/v1/agents/$AGENT_ID/narrator/leads" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"leads": [
{
"id": "0e9d8c7b-6a5f-4e3d-8c2b-1a0f9e8d7c6b",
"agent_id": "3f6b2c1e-8a4d-4f0b-9c2e-7d1a5b6e9f10",
"connection_id": "b7e2d9a4-1c3f-4e8a-9d6b-2f5c8a1e4d73",
"form_data": {
"name": "Jane",
"email": "jane@globex.com"
},
"page_url": "https://acme.com/demo",
"status": "new",
"notes": null,
"created_at": "2026-10-25T16:10:00.000Z"
}
],
"total": 1
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | Agent does not exist or is not in your workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: list_narrator_leads
PATCH/v1/agents/:id/narrator/leads/:leadIdUpdate / dismiss a narrator lead
#patch-v1-agents-id-narrator-leads-leadid
Set a presentation lead's status (new / contacted / converted / spam) and notes. Free.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string | yes | Agent id (UUID), from list_agents |
leadId | path | string | yes | |
status | body | string | one of: new, contacted, converted, spam | |
notes | body | string |
Request
curl -s -X PATCH "https://api.vendo-ai.com/v1/agents/$AGENT_ID/narrator/leads/$LEAD_ID" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: update_narrator_lead
DELETE/v1/agents/:id/narrator/leads/:leadIdDelete a presentation lead
#delete-v1-agents-id-narrator-leads-leadid
Permanently deletes a lead (the app's trash button). dry_run shows the lead; confirm deletes it.
Protected: send {"dry_run": true} to preview, then {"confirm": true} to execute (protocol).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Agent id |
leadId | path | string (uuid) | yes | |
dry_run | body | boolean | true = preview only (nothing happens) | |
confirm | body | boolean | true = execute. Send exactly one of dry_run / confirm |
Request
curl -s -X DELETE "https://api.vendo-ai.com/v1/agents/$AGENT_ID/narrator/leads/$LEAD_ID" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"confirm": true}'Response
{
"data": {
"success": true
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | confirmation_required | Neither dry_run nor confirm was sent |
| 404 | not_found | Agent or lead does not exist or is not in your workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
GET/v1/agents/:id/presentationGet the Slide Presenter
#get-v1-agents-id-presentation
The agent's current presentation (agent.presentation_slides, not the legacy presentations): PDF name, slides with text and presenter script (image URLs with include_images=true), look, call-to-action, presenter voice, published channels and the direct link.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Agent id |
include_images | query | boolean | Add slide image URLs |
Request
curl -s "https://api.vendo-ai.com/v1/agents/$AGENT_ID/presentation" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Show the full response (56 lines)
{
"data": {
"pdf_name": "Acme pitch.pdf",
"slides_count": 2,
"slides": [
{
"id": "slide-1",
"page_number": 1,
"extracted_text": "Acme CRM",
"narrator_script": "Welcome! ...",
"has_image": true
}
],
"look": {
"theme": "dark",
"backgroundColor": "#0f172b",
"buttonColor": "#3b82f6",
"voiceEnabled": true,
"chatEnabled": true
},
"look_saved": true,
"cta": {
"enabled": true,
"buttonText": "Book a demo",
"type": "link",
"linkUrl": "https://calendly.com/acme/demo"
},
"voice": {
"voice_id": "retell-Grace",
"speed": 1.05,
"emotion": "expressive",
"ambient": "",
"backchannel": true,
"end_call_s": 30,
"rules": "You are Anna, a sales representative at Acme...",
"languages": [
"en-US",
"uk-UA"
],
"volume": 1,
"responsiveness": 0.8,
"interruption_sensitivity": 0.5,
"llm": "balanced",
"screen_cards": true,
"saved_at": "2026-10-26T10:12:03.000Z"
},
"published_connections": [
{
"id": "b7e2d9a4-1c3f-4e8a-9d6b-2f5c8a1e4d73",
"type": "PRESENTATION_WIDGET",
"name": "acme.com"
}
],
"direct_link": "https://app.vendo-ai.com/narrator-page/3f6b2c1e-8a4d-4f0b-9c2e-7d1a5b6e9f10"
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | Agent does not exist or is not in your workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: get_presentation
PATCH/v1/agents/:id/presentation/ctaSet the call-to-action
#patch-v1-agents-id-presentation-cta
The presentation's call-to-action: a lead form (fields) or a link. Merge over the saved CTA; fields replaces the field list. Leads appear in GET /v1/agents/:id/narrator/leads.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Agent id |
enabled | body | boolean | ||
buttonText | body | string | ||
type | body | string | one of: form, link | |
linkUrl | body | string | http(s), required for type link | |
linkOpenInNewTab | body | boolean | ||
fields | body | array | ≤12 × {id?, type text|email|phone|textarea|select, label, placeholder?, required?, options? (select)} | |
successMessage | body | string |
Request
curl -s -X PATCH "https://api.vendo-ai.com/v1/agents/$AGENT_ID/presentation/cta" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"enabled": true,
"type": "form",
"buttonText": "Get a quote",
"fields": [
{
"id": "name",
"type": "text",
"label": "Name",
"required": true
},
{
"id": "email",
"type": "email",
"label": "Email",
"required": true
}
]
}'Response
{
"data": {
"cta": {
"enabled": true,
"buttonText": "Get a quote",
"type": "form",
"linkUrl": "",
"linkOpenInNewTab": true,
"fields": [
{
"id": "name",
"type": "text",
"label": "Name",
"required": true
},
{
"id": "email",
"type": "email",
"label": "Email",
"required": true
}
],
"successMessage": "Thank you! We will contact you soon."
}
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | forbidden_field | Body contains a field that is not settable here |
| 404 | not_found | Agent does not exist or is not in your workspace |
| 400 | invalid_request | Invalid field / missing linkUrl |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: set_presentation_cta
GET/v1/agents/:id/presentation/embedPresentation embed code & link
#get-v1-agents-id-presentation-embed
The direct link (narrator page) and the HTML snippet with the saved look, the same snippet the app copies. Replaces the legacy /presentations/embed (old creator).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Agent id |
Request
curl -s "https://api.vendo-ai.com/v1/agents/$AGENT_ID/presentation/embed" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"direct_link": "https://app.vendo-ai.com/narrator-page/3f6b2c1e-8a4d-4f0b-9c2e-7d1a5b6e9f10",
"embed_code": "<script>\n window.VendoNarrator = {\n agentId: '...',\n ...\n };\n</script>\n<div id=\"vendo-narrator\" style=\"width: 100%;\"></div>\n<script src=\"https://app.vendo-ai.com/widget/narrator-embed.js\" async></script>",
"has_slides": true,
"look_saved": true
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | Agent does not exist or is not in your workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
PATCH/v1/agents/:id/presentation/lookSet the presentation look
#patch-v1-agents-id-presentation-look
Theme, colours, radius, controls, autoplay, voice/chat on-off, Modern look, transition and texts. Only what you pass changes; modern and texts are merged with the saved values (the backend would replace them whole). Marks the look as saved (embed enabled).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Agent id |
theme | body | string | one of: dark, light, custom | |
backgroundColor | body | hex | ||
buttonColor | body | hex | ||
buttonTextColor | body | hex | ||
progressColor | body | hex | ||
textColor | body | hex | ||
borderRadius | body | number | 0–40 | |
showControls | body | boolean | ||
autoplay | body | boolean | ||
voiceEnabled | body | boolean | ||
chatEnabled | body | boolean | ||
design | body | string | one of: classic, modern | |
modern | body | object | background solid|gradient, bgFrom, bgTo, angle, glass, glassOpacity, blur, gradientButtons, accent2, panelSide right|left, presenterBubble off|left|right, font system|rounded|serif|mono, glow, voiceVisual portrait|liquid|waves | |
transition | body | string | one of: zoom, fade, slide | |
texts | body | object | chooseMode, voice, chat, placeholder, clickToStart; ""/null = default |
Request
curl -s -X PATCH "https://api.vendo-ai.com/v1/agents/$AGENT_ID/presentation/look" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"design": "modern",
"modern": {
"font": "rounded",
"voiceVisual": "liquid"
},
"texts": {
"clickToStart": "Tap to start"
}
}'Response
{
"data": {
"look": {
"theme": "dark",
"backgroundColor": "#0f172b",
"design": "modern",
"modern": {
"background": "gradient",
"font": "rounded",
"voiceVisual": "liquid"
},
"texts": {
"clickToStart": "Tap to start"
},
"voiceEnabled": true,
"chatEnabled": true
}
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | forbidden_field | Body contains a field that is not settable here |
| 404 | not_found | Agent does not exist or is not in your workspace |
| 400 | invalid_request | Invalid value |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
POST/v1/agents/:id/presentation/slidesSave slides (replaces the deck)
#post-v1-agents-id-presentation-slides
Saves the whole slide deck: add, remove, reorder, edit. Images by public URL or base64 (JPG/PNG/WebP/GIF ≤5 MB each, ≤18 MB total) are stored by the platform; omit the image of an existing slide id to keep it. Also re-detects the language and refreshes the presenter voice agent. PDF upload is not supported (the app renders PDFs in the browser), send page images.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Agent id |
pdf_name | body | string | Presentation name | |
slides | body | array | yes | 1–100 × {id?, page_number?, image_url? | image_base64?, extracted_text?, narrator_script?} |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/agents/$AGENT_ID/presentation/slides" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"pdf_name": "Acme pitch.pdf",
"slides": [
{
"id": "slide-1",
"image_url": "https://cdn.acme.com/deck/1.png",
"extracted_text": "Acme CRM",
"narrator_script": "Welcome! ..."
}
]
}'Response
{
"data": {
"pdf_name": "Acme pitch.pdf",
"slides": [
{
"id": "slide-1",
"page_number": 1,
"image_url": "https://api.vendo-ai.com/uploads/presentations/3f6b2c1e-8a4d-4f0b-9c2e-7d1a5b6e9f10/slide-1.png",
"extracted_text": "Acme CRM",
"narrator_script": "Welcome! ..."
}
]
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | forbidden_field | Body contains a field that is not settable here |
| 404 | not_found | Agent does not exist or is not in your workspace |
| 400 | invalid_request | Bad slide, image type or size |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
PATCH/v1/agents/:id/presentation/voiceSet the presenter's voice
#patch-v1-agents-id-presentation-voice
The Slide Presenter's own voice and model (the widget voice is untouched), the same settings as the app. Merge: values you omit keep the saved presentation voice (first time: the widget voice). Builds the presenter voice agent at the provider (up to ~1 min). Requires saved slides.
Credits: free (calls 17 credits/min)
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Agent id |
voice_id | body | string | Voice from GET /v1/agents/voices/catalog (required the first time) | |
speed | body | number | 0.7–1.3, 1 = normal | |
emotion | body | string | Delivery one of: neutral, expressive, calm, dynamic | |
languages | body | string[] | Retell locales (xx-YY, ≤20). More than one = multilingual | |
rules | body | string | Communication rules (≤8000 chars). Default text is generated when none is saved | |
ambient | body | string | Background sound one of: "", coffee-shop, call-center, convention-hall | |
ambient_sound_volume | body | number | 0–2 | |
backchannel | body | boolean | Short acknowledgements while the person talks | |
backchannel_frequency | body | number | 0–1 | |
end_call_s | body | number | Hang up after N s of silence, 10–120 | |
volume | body | number | 0–2 | |
responsiveness | body | number | 0–1 | |
interruption_sensitivity | body | number | 0–1 | |
dynamic_voice_speed | body | boolean | ||
dynamic_responsiveness | body | boolean | ||
expressive_mode | body | boolean | Ignored for voices that are not expressive in the catalogue | |
denoising_mode | body | string | one of: noise-cancellation, noise-and-background-speech-cancellation | |
reminder_seconds | body | number | Nudge a silent person after N s; 0 = off | |
handbook | body | object | Booleans: conversational_personality, natural_filler_words, high_empathy, echo_verification, speech_normalization, ai_disclosure, scope_boundaries (merged) | |
llm | body | string | Model one of: fast, balanced, smart | |
screen_cards | body | boolean | Interactive cards on screen during voice calls |
Request
curl -s -X PATCH "https://api.vendo-ai.com/v1/agents/$AGENT_ID/presentation/voice" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"voice_id": "retell-Grace",
"speed": 1.05,
"emotion": "expressive",
"languages": [
"en-US",
"uk-UA"
],
"llm": "balanced",
"screen_cards": true
}'Response
{
"data": {
"voice": {
"voice_id": "retell-Grace",
"speed": 1.05,
"emotion": "expressive",
"ambient": "",
"backchannel": true,
"end_call_s": 30,
"rules": "You are Anna, a sales representative at Acme...",
"languages": [
"en-US",
"uk-UA"
],
"volume": 1,
"responsiveness": 0.8,
"interruption_sensitivity": 0.5,
"llm": "balanced",
"screen_cards": true,
"saved_at": "2026-10-26T10:12:03.000Z"
}
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | forbidden_field | Body contains a field that is not settable here |
| 404 | not_found | Agent does not exist or is not in your workspace |
| 400 | invalid_request | Upload the presentation first / unknown voice_id / invalid value |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
GET/v1/agents/:id/presentationsPresentation (legacy alias)
#get-v1-agents-id-presentations
Legacy route kept for older clients. Fixed (2026-10-26): it read the retired presentation-creator record and answered 404 for every agent, also agents with slides. It is now an alias of GET /v1/agents/:id/presentation and returns the agent's current Slide Presenter deck (slides_count 0 when none is uploaded) plus a note. Read-only.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | Agent id |
include_images | query | boolean | Add slide image URLs |
Request
curl -s "https://api.vendo-ai.com/v1/agents/$AGENT_ID/presentations" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Show the full response (46 lines)
{
"data": {
"pdf_name": "Pitch deck.pdf",
"slides_count": 2,
"slides": [
{
"id": "slide-1",
"page_number": 1,
"extracted_text": "Vendo AI — AI sales agents",
"narrator_script": "Hi, I am Ava…",
"has_image": true
},
{
"id": "slide-2",
"page_number": 2,
"extracted_text": "Pricing",
"narrator_script": "Plans start at…",
"has_image": true
}
],
"look": {
"theme": "dark",
"design": "modern"
},
"look_saved": true,
"cta": {
"enabled": true,
"type": "form",
"buttonText": "Book a demo"
},
"voice": {
"voice_id": "retell-Grace",
"speed": 1
},
"published_connections": [
{
"id": "d2f8a1c4-6b3e-4d9a-8f7c-1e5b0a9d3c62",
"type": "PRESENTATION_WIDGET",
"name": "Slide Presenter",
"status": "ACTIVE"
}
],
"direct_link": "https://app.vendo-ai.com/narrator-page/a91d4f60-2c3b-4e7a-8f15-0b6c9d2e4a71"
},
"note": "Alias of GET /v1/agents/:id/presentation (the current Slide Presenter). Prefer that route."
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | Agent does not exist or is not in your workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
GET/v1/agents/:id/presentations/embedPresentation embed code (legacy alias)
#get-v1-agents-id-presentations-embed
Legacy route kept for older clients. Fixed (2026-10-26): always answered 404; now an alias of GET /v1/agents/:id/presentation/embed (direct link + embed snippet of the current Slide Presenter). Read-only.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | Agent id |
Request
curl -s "https://api.vendo-ai.com/v1/agents/$AGENT_ID/presentations/embed" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"direct_link": "https://app.vendo-ai.com/narrator-page/a91d4f60-2c3b-4e7a-8f15-0b6c9d2e4a71",
"embed_code": "<script>\n window.VendoNarrator = { agentId: 'a91d4f60-…', … };\n</script>\n<div id=\"vendo-narrator\" style=\"width: 100%;\"></div>\n<script src=\"https://app.vendo-ai.com/widget/narrator-embed.js\" async></script>",
"has_slides": true,
"look_saved": true
},
"note": "Alias of GET /v1/agents/:id/presentation/embed. Prefer that route."
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | Agent does not exist or is not in your workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
Google Meet 11
GET/v1/agents/:id/google-meetGoogle Meet settings and status
Google Meet status and settings of the agent: invite address (agent_email), connected, record status, auto-join config and counters. Fixed (2026-10-26): connected is false once the Meet record is DISCONNECTED (after DELETE /v1/agents/:id/google-meet it used to keep saying connected: true); a foreign or deleted agent id answers 404 (it answered 200 {connected:false}).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | Agent id |
Request
curl -s "https://api.vendo-ai.com/v1/agents/$AGENT_ID/google-meet" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"connected": true,
"id": "6e2b9c4a-3f1d-4a8e-b7c5-9d0f2e1a8b36",
"agent_id": "a91d4f60-2c3b-4e7a-8f15-0b6c9d2e4a71",
"agent_email": "agent-a91d4f60@meet.vendo-ai.com",
"status": "ACTIVE",
"google_account_email": null,
"config": {
"auto_join": true,
"join_before_minutes": 1,
"language": "en",
"meeting_mode": "voice_only",
"greeting_message": "Hi all, I am Ava."
},
"total_meetings": 6,
"total_minutes": 142
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | Agent does not exist or is not in your workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
DELETE/v1/agents/:id/google-meetDisconnect Google Meet
#delete-v1-agents-id-google-meet
Disconnects Google Meet like the app: removes the GOOGLE_MEET channel(s), turns auto-join off and marks the agent's Meet record DISCONNECTED, so GET /v1/agents/:id/google-meet then reports connected: false and the bot stops joining. Destructive protocol (dry_run / confirm). Scheduled meetings are not cancelled. Reconnect any time with POST /v1/agents/:id/google-meet/connect (re-activates the record).
Protected: send {"dry_run": true} to preview, then {"confirm": true} to execute (protocol).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Agent id |
dry_run | body | boolean | true = preview only (nothing happens) | |
confirm | body | boolean | true = execute. Send exactly one of dry_run / confirm |
Request
curl -s -X DELETE "https://api.vendo-ai.com/v1/agents/$AGENT_ID/google-meet" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"confirm": true}'Response
{
"data": {
"ok": true,
"removed_channels": [
"b7e2d9a4-1c3f-4e8a-9d6b-2f5c8a1e4d73"
],
"connected": false
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | confirmation_required | Neither dry_run nor confirm was sent |
| 404 | not_found | Agent does not exist or is not in your workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
GET/v1/agents/:id/google-meet/analyticsGoogle Meet analytics
#get-v1-agents-id-google-meet-analytics
Meetings, minutes, completed, missed and credits for a date range (the Meet channel page).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Agent id |
date_from | query | date | ||
date_to | query | date |
Request
curl -s "https://api.vendo-ai.com/v1/agents/$AGENT_ID/google-meet/analytics" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"totalMeetings": 4,
"totalMinutes": 96,
"completedMeetings": 3,
"missedMeetings": 1,
"creditsUsed": 1632,
"breakdown": {
"voiceCredits": 1632
}
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | Agent does not exist or is not in your workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
GET/v1/agents/:id/google-meet/auth-urlStart Google Meet OAuth (returns URL)
#get-v1-agents-id-google-meet-auth-url
Returns a Google OAuth URL. A person must open it in a browser and complete the consent screen; the Google Meet connection becomes active afterwards.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string | yes |
Request
curl -s "https://api.vendo-ai.com/v1/agents/$AGENT_ID/google-meet/auth-url" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
PATCH/v1/agents/:id/google-meet/configChange Google Meet settings
#patch-v1-agents-id-google-meet-config
Auto-join, minutes before start, greeting, language, meeting mode. Fixed: allow-listed (the backend merged any key), presentation mode checked against the slides, and saved through the channel so both copies stay in sync.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Agent id |
auto_join | body | boolean | ||
join_before_minutes | body | number | ||
greeting_message | body | string | ||
language | body | string | ||
meeting_mode | body | string | one of: voice_only, presentation |
Request
curl -s -X PATCH "https://api.vendo-ai.com/v1/agents/$AGENT_ID/google-meet/config" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"join_before_minutes": 2}'Response
{
"data": {
"connected": true,
"agent_email": "agent-3f6b2c1e@meet.vendo-ai.com",
"status": "ACTIVE",
"config": {
"auto_join": true,
"join_before_minutes": 2,
"language": "en",
"meeting_mode": "voice_only"
},
"total_meetings": 4,
"total_minutes": 96
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | forbidden_field | Body contains a field that is not settable here |
| 404 | not_found | Agent or Google Meet connection does not exist or is not in your workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
POST/v1/agents/:id/google-meet/connectConnect Google Meet (invite address)
#post-v1-agents-id-google-meet-connect
The app's current flow: no Google login, the agent gets its own address agent-<8>@meet.vendo-ai.com; invite it to a Google Calendar event with a Meet link and the agent joins. Creates or updates the GOOGLE_MEET channel with the settings.
Credits: free (meeting minutes billed)
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Agent id |
auto_join | body | boolean | Default true | |
join_before_minutes | body | number | 0–30, default 1 | |
greeting_message | body | string | ||
language | body | string | Default en | |
meeting_mode | body | string | presentation needs slides one of: voice_only, presentation |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/agents/$AGENT_ID/google-meet/connect" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"greeting_message": "Hello! I'\''m Anna, Acme'\''s AI assistant.",
"meeting_mode": "voice_only"
}'Response
{
"data": {
"connection": {
"id": "b7e2d9a4-1c3f-4e8a-9d6b-2f5c8a1e4d73",
"type": "GOOGLE_MEET",
"name": "Google Meet",
"status": "ACTIVE",
"google_meet_config": {
"auto_join": true,
"join_before_minutes": 1,
"greeting_message": "Hello! I'm Anna, Acme's AI assistant.",
"language": "en",
"meeting_mode": "voice_only",
"agent_email": "agent-3f6b2c1e@meet.vendo-ai.com"
}
},
"invite_address": "agent-3f6b2c1e@meet.vendo-ai.com",
"how_it_works": "Invite agent-3f6b2c1e@meet.vendo-ai.com to any Google Calendar event with a Google Meet link; the agent joins 1 min before start."
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | forbidden_field | Body contains a field that is not settable here |
| 404 | not_found | Agent does not exist or is not in your workspace |
| 400 | invalid_request | presentation mode without slides |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: connect_google_meet
GET/v1/agents/:id/google-meet/meetingsList meetings
#get-v1-agents-id-google-meet-meetings
Read-only list of meetings the agent has scheduled. Requires Google Meet connected first.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string | yes |
Request
curl -s "https://api.vendo-ai.com/v1/agents/$AGENT_ID/google-meet/meetings" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
GET/v1/agents/:id/google-meet/meetings/historyMeeting history
#get-v1-agents-id-google-meet-meetings-history
All meetings of this agent, newest first, paged.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Agent id |
page | query | integer | ||
limit | query | integer | 1–200 |
Request
curl -s "https://api.vendo-ai.com/v1/agents/$AGENT_ID/google-meet/meetings/history" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"meetings": [
{
"id": "5d1e8f2a-3b4c-4d6e-8f9a-0b1c2d3e4f56",
"title": "Acme × Globex intro",
"description": null,
"start_time": "2026-10-28T14:00:00.000Z",
"end_time": "2026-10-28T14:30:00.000Z",
"meet_link": "https://meet.google.com/abc-defg-hij",
"organizer_email": "ceo@globex.com",
"organizer_name": "Jane Doe",
"attendees": [
"ceo@globex.com",
"agent-3f6b2c1e@meet.vendo-ai.com"
],
"status": "completed",
"connection_id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
"agent_id": "3f6b2c1e-8a4d-4f0b-9c2e-7d1a5b6e9f10",
"has_transcript": false,
"agent_in_call": false,
"duration_minutes": 28
}
],
"total": 4,
"page": 1,
"limit": 20
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | Agent does not exist or is not in your workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
GET/v1/agents/:id/google-meet/meetings/:meetingIdGet a meeting of the agent
#get-v1-agents-id-google-meet-meetings-meetingid
One meeting of this agent with transcript, summary and action items. 404 for meetings of other agents or users.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Agent id |
meetingId | path | string (uuid) | yes |
Request
curl -s "https://api.vendo-ai.com/v1/agents/$AGENT_ID/google-meet/meetings/$MEETING_ID" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"id": "5d1e8f2a-3b4c-4d6e-8f9a-0b1c2d3e4f56",
"title": "Acme × Globex intro",
"description": null,
"start_time": "2026-10-28T14:00:00.000Z",
"end_time": "2026-10-28T14:30:00.000Z",
"meet_link": "https://meet.google.com/abc-defg-hij",
"organizer_email": "ceo@globex.com",
"organizer_name": "Jane Doe",
"attendees": [
"ceo@globex.com",
"agent-3f6b2c1e@meet.vendo-ai.com"
],
"status": "scheduled",
"connection_id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
"agent_id": "3f6b2c1e-8a4d-4f0b-9c2e-7d1a5b6e9f10",
"has_transcript": false,
"agent_in_call": false,
"transcript": {
"messages": []
},
"summary": null,
"action_items": null
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | Meeting does not exist or is not in your workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: get_meeting
GET/v1/agents/:id/google-meet/meetings/:meetingId/transcriptMeeting transcript
#get-v1-agents-id-google-meet-meetings-meetingid-transcript
Transcript messages, summary and action items (from the database, else from the recording service).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Agent id |
meetingId | path | string (uuid) | yes |
Request
curl -s "https://api.vendo-ai.com/v1/agents/$AGENT_ID/google-meet/meetings/$MEETING_ID/transcript" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"id": "5d1e8f2a-3b4c-4d6e-8f9a-0b1c2d3e4f56",
"title": "Acme × Globex intro",
"start_time": "2026-10-28T14:00:00.000Z",
"end_time": "2026-10-28T14:30:00.000Z",
"duration_minutes": 28,
"messages": [
{
"speaker": "Jane Doe",
"content": "Can you show pricing?",
"timestamp": "2026-10-28T14:03:10.000Z",
"is_agent": false
}
],
"summary": "Intro call...",
"action_items": [
"Send proposal"
],
"source": "database"
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | Meeting does not exist or is not in your workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
POST/v1/agents/:id/google-meet/syncSync Google Calendar meetings (OAuth flow)
#post-v1-agents-id-google-meet-sync
Only for agents connected through the old Google Calendar OAuth flow (GET /v1/agents/:id/google-meet/auth-url): pulls new, changed and deleted calendar events into the meetings list. Answers 200. Fixed (2026-10-26): without Google OAuth it answers 409 not_connected (it answered 401 unauthorized, which reads as a bad API key, or 2xx {error}); a foreign agent id answers 404.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | Agent id |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/agents/$AGENT_ID/google-meet/sync" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"synced": 2,
"meetings": [
{
"id": "0c7e4b2a-9d1f-4e6a-8b3c-5f2d1a0e9c47",
"title": "Demo with Northgate Dental",
"start_time": "2026-10-28T10:00:00.000Z",
"meet_link": "https://meet.google.com/abc-defg-hij",
"status": "scheduled"
}
]
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 409 | not_connected | The agent has no Google Calendar OAuth connection |
| 404 | not_found | Agent does not exist or is not in your workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
Telegram 10
GET/v1/agents/:id/telegramCurrent bot info
See the connected bot username + settings.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string | yes |
Request
curl -s "https://api.vendo-ai.com/v1/agents/$AGENT_ID/telegram" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
DELETE/v1/agents/:id/telegramDisconnect the Telegram bot
Detaches the bot from the agent: the webhook is removed and the bot stops answering (it keeps existing in Telegram; chat history is kept). Changed (2026-10-26): destructive protocol, {"dry_run": true} shows the bot (username, chats, messages) without changing anything; {"confirm": true} disconnects.
Protected: send {"dry_run": true} to preview, then {"confirm": true} to execute (protocol).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | Agent id |
dry_run | body | boolean | true = preview only: nothing is changed (HTTP 200) | |
confirm | body | boolean | true = execute. Send exactly one of dry_run / confirm |
Request
curl -s -X DELETE "https://api.vendo-ai.com/v1/agents/$AGENT_ID/telegram" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"dry_run": true}'Response
{
"data": {
"dry_run": true,
"would_do": "Disconnect Telegram bot @acme_sales_bot from agent \"Ava\": the webhook is removed and the bot stops answering.",
"bot": {
"username": "acme_sales_bot",
"first_name": "Acme Sales",
"total_chats": 14,
"total_messages": 233
},
"reversible": true,
"undo": "POST /v1/agents/:id/telegram/connect with the same bot_token (chat history is kept)."
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | confirmation_required | Neither dry_run nor confirm was sent (or both) |
| 404 | not_found | Agent does not exist, is not in your workspace, or has no bot |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: preview_disconnect_telegram_bot, disconnect_telegram_bot
GET/v1/agents/:id/telegram/analyticsTelegram analytics
#get-v1-agents-id-telegram-analytics
Chats, messages, voice calls, unique users and credits. Fixed: date_from / date_to are forwarded.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Agent id |
date_from | query | date | ||
date_to | query | date |
Request
curl -s "https://api.vendo-ai.com/v1/agents/$AGENT_ID/telegram/analytics" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"totalChats": 31,
"totalMessages": 402,
"voiceCalls": 6,
"textMessages": 396,
"uniqueUsers": 29,
"creditsUsed": 210,
"breakdown": {
"textCredits": 120,
"voiceCredits": 90
}
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | Agent does not exist or is not in your workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
GET/v1/agents/:id/telegram/chatsList Telegram chats
#get-v1-agents-id-telegram-chats
People and groups talking to the bot, paged.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Agent id |
page | query | integer | ||
limit | query | integer | 1–200 |
Request
curl -s "https://api.vendo-ai.com/v1/agents/$AGENT_ID/telegram/chats" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"chats": [
{
"id": "1b2c3d4e-5f60-4718-9a0b-c1d2e3f40516",
"chatId": "512345678",
"chatType": "private",
"username": "jane_d",
"firstName": "Jane",
"lastName": "Doe",
"isActive": true,
"messageCount": 14,
"lastMessageAt": "2026-10-26T08:40:00.000Z",
"createdAt": "2026-10-20T12:00:00.000Z"
}
],
"total": 1,
"page": 1,
"limit": 20,
"totalPages": 1
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | Agent does not exist or is not in your workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: list_telegram_chats
GET/v1/agents/:id/telegram/chats/:chatIdRead a Telegram chat
#get-v1-agents-id-telegram-chats-chatid
Messages of one chat incl. voice-call turns with recording URL and duration.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Agent id |
chatId | path | string (uuid) | yes | chats[].id |
Request
curl -s "https://api.vendo-ai.com/v1/agents/$AGENT_ID/telegram/chats/$CHAT_ID" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"chat": {
"id": "1b2c3d4e-5f60-4718-9a0b-c1d2e3f40516",
"username": "jane_d",
"firstName": "Jane"
},
"messages": [
{
"id": "m1",
"role": "user",
"type": "text",
"content": "Hi",
"createdAt": "2026-10-26T08:39:00.000Z",
"callId": null,
"recordingUrl": null,
"durationSeconds": null
}
]
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | Agent or chat does not exist or is not in your workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: get_telegram_chat
POST/v1/agents/:id/telegram/chats/:chatId/sendSend an operator message
#post-v1-agents-id-telegram-chats-chatid-send
Human takeover: sends a REAL Telegram message from the bot to this person. dry_run shows the recipient and text; confirm sends.
Protected: send {"dry_run": true} to preview, then {"confirm": true} to execute (protocol).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Agent id |
chatId | path | string (uuid) | yes | |
message | body | string | yes | 1–4096 chars |
dry_run | body | boolean | true = preview only (nothing happens) | |
confirm | body | boolean | true = execute. Send exactly one of dry_run / confirm |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/agents/$AGENT_ID/telegram/chats/$CHAT_ID/send" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"message": "Hi Jane, Mark here from Acme — I'\''ll call you at 3pm.", "confirm": true}'Response
{
"data": {
"success": true,
"message": {
"id": "m2",
"role": "assistant",
"type": "text",
"content": "Hi Jane, Mark here from Acme — I'll call you at 3pm.",
"createdAt": "2026-10-26T09:00:00.000Z"
}
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | confirmation_required | Neither dry_run nor confirm was sent |
| 404 | not_found | Agent or chat does not exist or is not in your workspace |
| 400 | invalid_request | Empty message / bot not active |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
POST/v1/agents/:id/telegram/connectConnect a Telegram bot
#post-v1-agents-id-telegram-connect
Connects a bot created with @BotFather (bot_token) to the agent: the webhook is set and the agent answers every chat of the bot. Answers 201. Fixed (2026-10-26): a missing or malformed bot_token answers 400 invalid_request (it answered 201 with an error object). Optional {"dry_run": true} validates the token with Telegram and shows the bot and whether it replaces the current one, nothing is connected (HTTP 200).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | Agent id |
bot_token | body | string | yes | HTTP API token from @BotFather, e.g. 123456789:AA… |
dry_run | body | boolean | true = validate and preview only |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/agents/$AGENT_ID/telegram/connect" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"bot_token": "123456789:AAH4k…"}'Response
{
"data": {
"connected": true,
"bot_username": "acme_sales_bot",
"bot_first_name": "Acme Sales",
"webhook_set": true
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | bot_token missing or not a Telegram token |
| 404 | not_found | Agent does not exist or is not in your workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: connect_telegram_bot
POST/v1/agents/:id/telegram/reconfigureReconfigure bot settings
#post-v1-agents-id-telegram-reconfigure
Re-apply the bot's commands and menu button (for private and group chats), use when the bot menu looks wrong. Free.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string | yes | Agent id (UUID), from list_agents |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/agents/$AGENT_ID/telegram/reconfigure" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
PATCH/v1/agents/:id/telegram/voiceVoice calls on/off
#patch-v1-agents-id-telegram-voice
Shows or hides the bot's VOICE button. Fixed: the backend reads voice_enabled, the old route sent enabled, which removed the VOICE button while voice stayed on. Accepts enabled or voice_enabled.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Agent id |
enabled | body | boolean | yes | Or voice_enabled |
Request
curl -s -X PATCH "https://api.vendo-ai.com/v1/agents/$AGENT_ID/telegram/voice" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"enabled": false}'Response
{
"data": {
"success": true,
"voice_enabled": false
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | forbidden_field | Body contains a field that is not settable here |
| 404 | not_found | Agent or bot does not exist or is not in your workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
PATCH/v1/agents/:id/telegram/voice-settingsTelegram voice settings
#patch-v1-agents-id-telegram-voice-settings
The Telegram bot's own voice (separate from the widget): the full Voice v2 set plus the mini app look. Merge over the saved Telegram voice. Creates/updates the Telegram voice agent at the provider (up to ~1 min). Needs a connected bot.
Credits: free (calls billed per minute)
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Agent id |
voice_id | body | string | Voice from GET /v1/agents/voices/catalog (required the first time) | |
speed | body | number | 0.7–1.3, 1 = normal | |
emotion | body | string | Delivery one of: neutral, expressive, calm, dynamic | |
languages | body | string[] | Retell locales (xx-YY, ≤20). More than one = multilingual | |
rules | body | string | Communication rules (≤8000 chars). Default text is generated when none is saved | |
ambient | body | string | Background sound one of: "", coffee-shop, call-center, convention-hall | |
ambient_sound_volume | body | number | 0–2 | |
backchannel | body | boolean | Short acknowledgements while the person talks | |
backchannel_frequency | body | number | 0–1 | |
end_call_s | body | number | Hang up after N s of silence, 10–120 | |
volume | body | number | 0–2 | |
responsiveness | body | number | 0–1 | |
interruption_sensitivity | body | number | 0–1 | |
dynamic_voice_speed | body | boolean | ||
dynamic_responsiveness | body | boolean | ||
expressive_mode | body | boolean | Ignored for voices that are not expressive in the catalogue | |
denoising_mode | body | string | one of: noise-cancellation, noise-and-background-speech-cancellation | |
reminder_seconds | body | number | Nudge a silent person after N s; 0 = off | |
handbook | body | object | Booleans: conversational_personality, natural_filler_words, high_empathy, echo_verification, speech_normalization, ai_disclosure, scope_boundaries (merged) | |
llm | body | string | Model one of: fast, balanced, smart | |
screen_cards | body | boolean | Interactive cards on screen during voice calls | |
visual | body | string | Mini app look one of: portrait, liquid, waves |
Request
curl -s -X PATCH "https://api.vendo-ai.com/v1/agents/$AGENT_ID/telegram/voice-settings" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"voice_id": "retell-Grace",
"speed": 1.05,
"emotion": "expressive",
"languages": [
"en-US",
"uk-UA"
],
"llm": "balanced",
"screen_cards": true,
"visual": "waves"
}'Response
{
"data": {
"success": true,
"voice_agent_ready": true,
"voice": {
"voice_id": "retell-Grace",
"speed": 1.05,
"emotion": "expressive",
"ambient": "",
"backchannel": true,
"end_call_s": 30,
"rules": "You are Anna, a sales representative at Acme...",
"languages": [
"en-US",
"uk-UA"
],
"volume": 1,
"responsiveness": 0.8,
"interruption_sensitivity": 0.5,
"llm": "balanced",
"screen_cards": true,
"saved_at": "2026-10-26T10:12:03.000Z",
"visual": "waves"
}
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | forbidden_field | Body contains a field that is not settable here |
| 404 | not_found | Agent does not exist or is not in your workspace |
| 400 | invalid_request | Connect the bot first / unknown voice_id |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
Voice Call (phone) 6
GET/v1/agents/:id/connections/:cid/phone-statsVoice Call channel counters
#get-v1-agents-id-connections-cid-phone-stats
Calls attempted, completed, successful, minutes and credits of one PHONE_CALL channel.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Agent id |
cid | path | string (uuid) | yes | Channel (connection) id |
Request
curl -s "https://api.vendo-ai.com/v1/agents/$AGENT_ID/connections/$CONNECTION_ID/phone-stats" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"total": 42,
"attempted": 42,
"completed": 30,
"successful": 9,
"total_minutes": 61,
"total_credits": 1525
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | Channel does not exist or is not in your workspace |
| 400 | invalid_request | Not a PHONE_CALL channel |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
GET/v1/agents/:id/phone/callsPhone calls of the agent
Calls of this agent (campaign, test and inbound) with transcript, recording URL, summary, outcome, sentiment, duration and credits, plus totals.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Agent id |
limit | query | integer | 1–500, default 100 |
Request
curl -s "https://api.vendo-ai.com/v1/agents/$AGENT_ID/phone/calls" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Show the full response (33 lines)
{
"data": {
"stats": {
"total_calls": 12,
"answered_calls": 9,
"total_minutes": 21,
"total_credits": 525,
"by_outcome": {
"interested": 3
},
"by_sentiment": {
"positive": 5
}
},
"calls": [
{
"id": "e1d2c3b4-a596-4877-8695-a4b3c2d1e0f9",
"is_test_call": true,
"is_inbound": false,
"contact_phone": "+447700900123",
"status": "completed",
"call_outcome": "interested",
"call_sentiment": "positive",
"call_duration_seconds": 84,
"transcript": "Agent: Hello!...",
"recording_url": "https://dxc03zgurdly9.cloudfront.net/rec_xyz.wav",
"summary": "Interested, wants a demo",
"credits_used": 37,
"created_at": "2026-10-26T08:00:00.000Z"
}
]
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | Agent does not exist or is not in your workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: list_phone_calls
POST/v1/agents/:id/phone/generate-scriptDraft a call script
#post-v1-agents-id-phone-generate-script
Builds a call script and greeting from the agent's sales script for a call objective, in the agent's language. Free; not saved.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Agent id |
call_objective | body | string | Default book_meeting one of: sell, inform, book_meeting, collect_info, qualify_lead | |
voice_gender | body | string | one of: male, female |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/agents/$AGENT_ID/phone/generate-script" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"call_objective": "book_meeting"}'Response
{
"data": {
"script": "You are Anna, a professional saleswoman for Acme...",
"greeting": "Hello! This is Anna from Acme. Do you have a moment to talk?"
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | Agent does not exist or is not in your workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
POST/v1/agents/:id/phone/setupSet up the Voice Call channel
#post-v1-agents-id-phone-setup
Mirrors the app's Voice Call wizard: attaches one of YOUR numbers, saves the PHONE_CALL channel, creates/updates the outbound (and, with accept_incoming, inbound) voice agent at the provider and links the number. Script is generated when omitted. Omitted values keep the channel's saved ones. Up to ~1 min. Changed (2026-10-26): attaches a real number and creates voice agents, so it follows the destructive protocol, {"dry_run": true} returns what would be created/updated (channel, number, objective, voice, inbound on/off) without touching anything; {"confirm": true} runs the setup. The dry run answers {"data": {"dry_run": true, "would_do", "channel": {"id", "action": "create|update"}, "phone_number": {"id", "phone_number"}, "call_objective", "call_script", "voice", "accept_incoming", "max_call_duration_seconds", "cost", "reversible", "undo"}}.
Credits: free (calls 25 credits/min) · Protected: send {"dry_run": true} to preview, then {"confirm": true} to execute (protocol).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Agent id |
phone_number_id | body | string (uuid) | Required the first time (GET /v1/phone/numbers) | |
connection_id | body | string (uuid) | Existing PHONE_CALL channel | |
call_objective | body | string | one of: sell, inform, book_meeting, collect_info, qualify_lead | |
call_script | body | string | ||
greeting_message | body | string | ||
voice_id | body | string | ||
speed | body | number | 0.7–1.3 | |
emotion | body | string | one of: neutral, expressive, calm, dynamic | |
language | body | string | one of: auto, multi | |
communication_rules | body | string | ||
voice_gender | body | string | one of: male, female | |
accept_incoming | body | boolean | Answer calls to the number | |
ambient_sound | body | string | one of: "", coffee-shop, call-center, convention-hall | |
enable_backchannel | body | boolean | ||
end_call_silence_seconds | body | number | 10–120, default 30 | |
reminder_trigger_seconds | body | number | 5–60, default 10 | |
max_call_duration_seconds | body | number | 30–1800, default 300 | |
dry_run | body | boolean | true = preview only: nothing is changed (HTTP 200) | |
confirm | body | boolean | true = execute. Send exactly one of dry_run / confirm |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/agents/$AGENT_ID/phone/setup" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"phone_number_id": "4c8e2a6f-1d3b-4f7a-9e5c-0b2d8f6a4c13",
"call_objective": "book_meeting",
"accept_incoming": true,
"dry_run": true
}'Response
{
"data": {
"connection": {
"id": "b7e2d9a4-1c3f-4e8a-9d6b-2f5c8a1e4d73",
"type": "PHONE_CALL",
"name": "Phone Call Outreach",
"phone_call_config": {
"phone_number": "+14155550123",
"call_objective": "book_meeting",
"accept_incoming": true,
"voice_id": "11labs-Adrian",
"voice_agent_ready": true,
"inbound_agent_ready": true
}
},
"voice_agent_ready": true,
"inbound_enabled": true,
"phone_number": "+14155550123"
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | forbidden_field | Body contains a field that is not settable here |
| 404 | not_found | Agent, channel or phone number does not exist or is not in your workspace |
| 400 | confirmation_required | Neither dry_run nor confirm was sent (or both) |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
POST/v1/agents/:id/phone/test-callTest call (real phone)
#post-v1-agents-id-phone-test-call
Dials a REAL phone: the agent calls to_number from the channel's number (max 2 minutes, billed 25 credits/min per started 10 s). Requires a completed phone setup with a number you own. dry_run / confirm.
Credits: ≤50 credits (25/min) · Protected: send {"dry_run": true} to preview, then {"confirm": true} to execute (protocol).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Agent id |
to_number | body | string | yes | E.164 |
connection_id | body | string (uuid) | PHONE_CALL channel | |
dry_run | body | boolean | true = preview only (nothing happens) | |
confirm | body | boolean | true = execute. Send exactly one of dry_run / confirm |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/agents/$AGENT_ID/phone/test-call" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"to_number": "+447700900123", "dry_run": true}'Response
{
"data": {
"dry_run": true,
"would_do": "Call +447700900123 from +14155550123 with this agent (max 2 minutes)",
"estimated": {
"max_minutes": 2,
"credits_per_minute": 25,
"max_credits": 50
},
"reversible": false
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | confirmation_required | Neither dry_run nor confirm was sent |
| 404 | not_found | Agent or channel does not exist or is not in your workspace |
| 400 | invalid_request | Bad number / phone setup missing |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
GET/v1/phone/numbersList my phone numbers
Active phone numbers already on your account (per user). Buying numbers is not available in the API (owner decision), buy in the app.
Parameters
No parameters.
Request
curl -s "https://api.vendo-ai.com/v1/phone/numbers" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": [
{
"id": "6f5e4d3c-2b1a-4098-8776-655443322110",
"phone_number": "+14155550123",
"area_code": "415",
"nickname": null,
"status": "ACTIVE",
"country": "US",
"provider": "retell",
"number_type": "local",
"monthly_cost_credits": 200,
"call_rate_credits_per_minute": 25,
"next_billing_at": "2026-11-20T00:00:00.000Z",
"created_at": "2026-10-20T00:00:00.000Z"
}
]
}Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: list_phone_numbers
WhatsApp 3
GET/v1/whatsapp/accountsList WhatsApp accounts
WhatsApp accounts connected to your user. Account-level: the backend does not tie them to one agent (auto-replies use your latest campaign's agent).
Parameters
No parameters.
Request
curl -s "https://api.vendo-ai.com/v1/whatsapp/accounts" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": [
{
"id": "2a3b4c5d-6e7f-4809-9a1b-2c3d4e5f6a7b",
"phone_number": "+447700900456",
"display_name": "Acme Sales",
"status": "ok",
"created_at": "2026-10-21T10:00:00.000Z"
}
]
}Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
POST/v1/whatsapp/accounts/connectGet a WhatsApp connect link
#post-v1-whatsapp-accounts-connect
Returns a one-time hosted link; a human opens it and scans the QR code with WhatsApp (Linked devices), which links that real number to the workspace. Poll GET /v1/whatsapp/accounts until status is ok. Changed (2026-10-26): dry_run / confirm, {"dry_run": true} explains the effect and lists the accounts already connected without creating a link; {"confirm": true} returns the link.
Protected: send {"dry_run": true} to preview, then {"confirm": true} to execute (protocol).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
dry_run | body | boolean | true = preview only: nothing is changed (HTTP 200) | |
confirm | body | boolean | true = execute. Send exactly one of dry_run / confirm |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/whatsapp/accounts/connect" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"confirm": true}'Response
{
"data": {
"url": "https://account.unipile.com/hosted/abc123",
"how_to": "Open the URL in a browser and scan the QR code with WhatsApp..."
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | confirmation_required | Neither dry_run nor confirm was sent (or both) |
| 400 | forbidden_field | Body has fields other than dry_run / confirm |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: preview_connect_whatsapp_account, connect_whatsapp_account
DELETE/v1/whatsapp/accounts/:accountIdDisconnect a WhatsApp account
#delete-v1-whatsapp-accounts-accountid
Disconnects and deletes the account at the provider; auto-replies stop and reconnecting needs a new QR scan. dry_run / confirm.
Protected: send {"dry_run": true} to preview, then {"confirm": true} to execute (protocol).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
accountId | path | string (uuid) | yes | |
dry_run | body | boolean | true = preview only (nothing happens) | |
confirm | body | boolean | true = execute. Send exactly one of dry_run / confirm |
Request
curl -s -X DELETE "https://api.vendo-ai.com/v1/whatsapp/accounts/$ACCOUNT_ID" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"confirm": true}'Response
{
"data": {
"success": true
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | confirmation_required | Neither dry_run nor confirm was sent |
| 404 | not_found | WhatsApp account does not exist or is not in your workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: preview_disconnect_whatsapp_account, disconnect_whatsapp_account
Conversations & recordings 5
GET/v1/agents/:id/connections/:cid/conversationsList conversations (paged)
#get-v1-agents-id-connections-cid-conversations
Conversations of a widget/presentation channel with messages, mode, visitor contact and page. Fixed: page and limit (1–500) are forwarded (was always the first 20).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Agent id |
cid | path | string (uuid) | yes | Channel (connection) id |
page | query | integer | Default 1 | |
limit | query | integer | 1–500, default 20 |
Request
curl -s "https://api.vendo-ai.com/v1/agents/$AGENT_ID/connections/$CONNECTION_ID/conversations" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"conversations": [
{
"id": "7c6b5a49-3827-4615-a504-f3e2d1c0b9a8",
"mode": "voice",
"visitor_name": "Jane",
"visitor_email": "jane@globex.com",
"page_url": "https://acme.com/pricing",
"messages": [
{
"role": "user",
"content": "How much is Pro?"
}
],
"created_at": "2026-10-25T15:00:00.000Z"
}
],
"total": 57,
"page": 1,
"limit": 20
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | Channel does not exist or is not in your workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
GET/v1/agents/:id/connections/:cid/conversations/:conversationId/recordingVoice recordings of a conversation
#get-v1-agents-id-connections-cid-conversations-conversationid-recording
Recording URLs (WAV on the voice provider's CDN), duration and start of the voice calls in one conversation (up to 10 calls; cached 10 min).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Agent id |
cid | path | string (uuid) | yes | Channel (connection) id |
conversationId | path | string (uuid) | yes |
Request
curl -s "https://api.vendo-ai.com/v1/agents/$AGENT_ID/connections/$CONNECTION_ID/conversations/$CONVERSATION_ID/recording" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"conversation_id": "7c6b5a49-3827-4615-a504-f3e2d1c0b9a8",
"recordings": [
{
"url": "https://dxc03zgurdly9.cloudfront.net/rec_abc.wav",
"duration_ms": 95000,
"started_at": "2026-10-25T15:01:10.000Z",
"status": "ended"
}
]
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | Channel or conversation does not exist or is not in your workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
GET/v1/linkedin/conversationsLinkedIn conversations
#get-v1-linkedin-conversations
LinkedIn chats of the workspace, optionally of one campaign and/or account, with last message, pending AI reply, takeover flag and outreach status.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
campaign_id | query | uuid | ||
account_id | query | uuid | ||
limit | query | integer | 1–100 (default 50). | |
offset | query | integer | Rows to skip (default 0). |
Request
curl -s "https://api.vendo-ai.com/v1/linkedin/conversations" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"conversations": [
{
"chat_id": "Xk3pQ9vR2sT8uY1w",
"contact_name": "James Whitfield",
"contact_provider_id": "ACoAAB12cdE",
"last_message": "Sounds good, Thursday works",
"last_message_at": "2026-10-26T11:02:00.000Z",
"message_count": 5,
"has_pending_auto_reply": true,
"last_sender": "contact",
"outreach_id": "5f1c2a9e-8b7d-4c3e-9a61-2d4f7b0c1e83",
"company_name": "Brightlane",
"outreach_status": "replied",
"human_takeover": false,
"contact_linkedin_url": "https://www.linkedin.com/in/jwhitfield"
}
],
"total": 27
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | A parameter has the wrong type, range or enum value. |
| 404 | not_found | Campaign or account not in the workspace. |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
GET/v1/linkedin/conversations/:chatId/messagesMessages of a LinkedIn chat
#get-v1-linkedin-conversations-chatid-messages
All messages of one chat, oldest first. chatId is the provider chat id from the conversations list (not a UUID).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
chatId | path | string | yes |
Request
curl -s "https://api.vendo-ai.com/v1/linkedin/conversations/$CHAT_ID/messages" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": [
{
"id": "5f1c2a9e-8b7d-4c3e-9a61-2d4f7b0c1e83",
"chat_id": "Xk3pQ9vR2sT8uY1w",
"text": "Thanks for connecting, James!",
"sender_type": "self",
"sent_by": "ai",
"message_timestamp": "2026-10-25T09:00:00.000Z"
},
{
"id": "b2f6d8a4-9e1c-4f3b-a7d2-6c0e5b8f1a49",
"chat_id": "Xk3pQ9vR2sT8uY1w",
"text": "Sounds good, Thursday works",
"sender_type": "contact",
"sender_name": "James Whitfield",
"message_timestamp": "2026-10-26T11:02:00.000Z"
}
]
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_parameter | Malformed chat id. |
| 404 | not_found | No message of this chat is visible to the workspace. |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
POST/v1/linkedin/conversations/:chatId/messagesSend a LinkedIn message
#post-v1-linkedin-conversations-chatid-messages
Sends a REAL LinkedIn message in the chat for 5 credits; the AI stops messaging the contact (human takeover) and the CRM lead moves to Manager Dialog. Requires a connected account owned by the key's user. dry_run shows recipient, text, cost and whether the balance suffices.
Credits: 5 credits · Protected: send {"dry_run": true} to preview, then {"confirm": true} to execute (protocol).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
chatId | path | string | yes | |
text | body | string | yes | ≤8000 |
dry_run | body | boolean | true = preview only, nothing happens. | |
confirm | body | boolean | true = execute. Exactly one of dry_run / confirm is required. |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/linkedin/conversations/$CHAT_ID/messages" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"confirm": true, "text": "Great — I'\''ll send an invite for Thursday 3pm."}'Response
{
"data": {
"id": "5f1c2a9e-8b7d-4c3e-9a61-2d4f7b0c1e83",
"chat_id": "Xk3pQ9vR2sT8uY1w",
"text": "Great — I'll send an invite for Thursday 3pm.",
"sender_type": "self",
"sent_by": "human",
"message_timestamp": "2026-10-26T11:30:00.000Z"
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | confirmation_required | Neither dry_run nor confirm was sent. |
| 400 | invalid_request | A parameter has the wrong type, range or enum value. |
| 404 | not_found | Chat not visible to the workspace. |
| 409 | no_own_linkedin_account | The key user has no connected account. |
| 402 | insufficient_credits | Balance below 5 credits. |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
Message previews 3
POST/v1/agents/:id/preview/emailPreview an agent email
#post-v1-agents-id-preview-email
Renders a sample email from the agent. Previously the request body was ignored; now mode, prompt, tone, customInstructions, variant, followUp {index, delayDays, previous} and sample render exactly like a campaign would. Without these fields the behaviour is unchanged. No sending, no credits.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | Agent id. |
mode | body | string | one of: ai, fixed | |
prompt | body | string | ||
tone | body | string | one of: formal, casual, friendly | |
customInstructions | body | string | ||
variant | body | object | ||
followUp | body | object | ||
sample | body | object |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/agents/$AGENT_ID/preview/email" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"prompt": "Two sentences, mention their hiring", "tone": "casual"}'Response
{
"data": {
"mode": "ai",
"subject": "Hiring 3 SDRs at Acme?",
"body": "Hi Alex, saw Acme is hiring three SDRs..."
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | Agent not in the workspace. |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: preview_agent_email
POST/v1/agents/:id/preview/linkedin-followupPreview the LinkedIn follow-up message
#post-v1-agents-id-preview-linkedin-followup
Render a sample first follow-up (after the connection is accepted). No sending, no cost.
Protected: send {"dry_run": true} to preview, then {"confirm": true} to execute (protocol).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string | yes | |
dry_run | body | boolean | true = preview only (nothing happens) | |
confirm | body | boolean | true = execute. Send exactly one of dry_run / confirm |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/agents/$AGENT_ID/preview/linkedin-followup" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"dry_run": true}'Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
POST/v1/agents/:id/preview/linkedin-invitePreview the LinkedIn invite text
#post-v1-agents-id-preview-linkedin-invite
Render a sample LinkedIn connection request from this agent. No sending, no cost.
Protected: send {"dry_run": true} to preview, then {"confirm": true} to execute (protocol).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string | yes | |
dry_run | body | boolean | true = preview only (nothing happens) | |
confirm | body | boolean | true = execute. Send exactly one of dry_run / confirm |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/agents/$AGENT_ID/preview/linkedin-invite" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"dry_run": true}'Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
Agent analytics & credits 7
GET/v1/agents/credits-spentWhole-workspace agent credit breakdown
Credits spent by every agent of the workspace, for the agents dashboard. Read-only.
Parameters
No parameters.
Request
curl -s "https://api.vendo-ai.com/v1/agents/credits-spent" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
GET/v1/agents/dashboard/analyticsDashboard summary
#get-v1-agents-dashboard-analytics
Older overview numbers of all agents (superseded by get_analytics_agents). Read-only.
Parameters
No parameters.
Request
curl -s "https://api.vendo-ai.com/v1/agents/dashboard/analytics" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: get_agents_dashboard
GET/v1/agents/with-statsList agents with connection + credit stats
Same as list_agents but includes per-agent stats (connections, credits spent, status).
Parameters
No parameters.
Request
curl -s "https://api.vendo-ai.com/v1/agents/with-stats" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
GET/v1/agents/:id/connections/:cid/analyticsWidget channel analytics
#get-v1-agents-id-connections-cid-analytics
The channel page numbers for a date range: widget loaded / activated / chats started / voice started, and estimated credits (text messages, voice minutes).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Agent id |
cid | path | string (uuid) | yes | Channel (connection) id |
date_from | query | date | ||
date_to | query | date |
Request
curl -s "https://api.vendo-ai.com/v1/agents/$AGENT_ID/connections/$CONNECTION_ID/analytics" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"events": {
"widgetLoaded": 1520,
"widgetActivated": 210,
"chatStarted": 88,
"voiceStarted": 19
},
"credits": {
"estimatedCredits": 412,
"breakdown": {
"textMessages": 640,
"voiceMinutes": 21,
"textCredits": 118,
"voiceCredits": 294
}
}
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | Channel does not exist or is not in your workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
GET/v1/agents/:id/connections/:cid/statsPer-connection stats
#get-v1-agents-id-connections-cid-stats
Conversations, messages, unique visitors, last activity of one channel. For widgets prefer get_connection_analytics (fresher). Read-only.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string | yes | Agent id (UUID), from list_agents |
cid | path | string | yes | Channel (connection) id (UUID), from list_agent_connections |
Request
curl -s "https://api.vendo-ai.com/v1/agents/$AGENT_ID/connections/$CONNECTION_ID/stats" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
GET/v1/agents/:id/credits-spentCredits this agent has spent
#get-v1-agents-id-credits-spent
Credits this agent has spent, as shown on the agent card. Read-only.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string | yes |
Request
curl -s "https://api.vendo-ai.com/v1/agents/$AGENT_ID/credits-spent" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
GET/v1/agents/:id/credits-spent/connectionsPer-connection credit breakdown
#get-v1-agents-id-credits-spent-connections
Credits this agent has spent, broken down per channel (connection). Read-only.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string | yes |
Request
curl -s "https://api.vendo-ai.com/v1/agents/$AGENT_ID/credits-spent/connections" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
Meetings API 6
The meetings calendar: meetings booked by your agents, transcripts, cancelling and removing the agent from a meeting.
Meetings calendar 6
GET/v1/meetingsMeetings calendar
Google Meet meetings of all your agents in a range (default next 7 days, max 62), the calendar page. Cancelled meetings are excluded. Meetings are per user: a teammate's key sees the teammate's meetings.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
start | query | datetime | ISO | |
end | query | datetime | ISO | |
agent_id | query | string (uuid) | Filter |
Request
curl -s "https://api.vendo-ai.com/v1/meetings" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"meetings": [
{
"id": "5d1e8f2a-3b4c-4d6e-8f9a-0b1c2d3e4f56",
"title": "Acme × Globex intro",
"description": null,
"start_time": "2026-10-28T14:00:00.000Z",
"end_time": "2026-10-28T14:30:00.000Z",
"meet_link": "https://meet.google.com/abc-defg-hij",
"organizer_email": "ceo@globex.com",
"organizer_name": "Jane Doe",
"attendees": [
"ceo@globex.com",
"agent-3f6b2c1e@meet.vendo-ai.com"
],
"status": "scheduled",
"connection_id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
"agent_id": "3f6b2c1e-8a4d-4f0b-9c2e-7d1a5b6e9f10",
"has_transcript": false,
"agent_in_call": false
}
],
"start": "2026-10-26T00:00:00.000Z",
"end": "2026-11-02T00:00:00.000Z"
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | Bad range |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: list_meetings
GET/v1/meetings/agentsAgents with Google Meet
Agents that have an active Google Meet connection (calendar sidebar filter).
Parameters
No parameters.
Request
curl -s "https://api.vendo-ai.com/v1/meetings/agents" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": [
{
"id": "3f6b2c1e-8a4d-4f0b-9c2e-7d1a5b6e9f10",
"name": "Anna",
"avatar_url": "avatar-2",
"meeting_mode": "voice_only"
}
]
}Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: list_meeting_agents
GET/v1/meetings/:meetingIdMeeting details
One meeting with transcript, summary, action items, join/leave times and duration (the meeting page).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
meetingId | path | string (uuid) | yes |
Request
curl -s "https://api.vendo-ai.com/v1/meetings/$MEETING_ID" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"id": "5d1e8f2a-3b4c-4d6e-8f9a-0b1c2d3e4f56",
"title": "Acme × Globex intro",
"description": null,
"start_time": "2026-10-28T14:00:00.000Z",
"end_time": "2026-10-28T14:30:00.000Z",
"meet_link": "https://meet.google.com/abc-defg-hij",
"organizer_email": "ceo@globex.com",
"organizer_name": "Jane Doe",
"attendees": [
"ceo@globex.com",
"agent-3f6b2c1e@meet.vendo-ai.com"
],
"status": "scheduled",
"connection_id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
"agent_id": "3f6b2c1e-8a4d-4f0b-9c2e-7d1a5b6e9f10",
"has_transcript": false,
"agent_in_call": false,
"transcript": null,
"summary": null,
"action_items": null,
"joined_at": null,
"left_at": null,
"duration_minutes": null
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | Meeting does not exist or is not in your workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: get_meeting
POST/v1/meetings/:meetingId/cancelCancel a meeting for the agent
#post-v1-meetings-meetingid-cancel
The agent will not join this meeting (the calendar event itself is unchanged). Only scheduled meetings that have not started. No undo → dry_run / confirm.
Protected: send {"dry_run": true} to preview, then {"confirm": true} to execute (protocol).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
meetingId | path | string (uuid) | yes | |
dry_run | body | boolean | true = preview only (nothing happens) | |
confirm | body | boolean | true = execute. Send exactly one of dry_run / confirm |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/meetings/$MEETING_ID/cancel" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"confirm": true}'Response
{
"data": {
"success": true
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | confirmation_required | Neither dry_run nor confirm was sent |
| 404 | not_found | Meeting does not exist or is not in your workspace |
| 400 | invalid_request | Not scheduled or already started |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
POST/v1/meetings/:meetingId/remove-agentRemove the agent from a live call
#post-v1-meetings-meetingid-remove-agent
The agent's bot leaves the live call now. Only for in-progress meetings with the agent in the call. dry_run / confirm.
Protected: send {"dry_run": true} to preview, then {"confirm": true} to execute (protocol).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
meetingId | path | string (uuid) | yes | |
dry_run | body | boolean | true = preview only (nothing happens) | |
confirm | body | boolean | true = execute. Send exactly one of dry_run / confirm |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/meetings/$MEETING_ID/remove-agent" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"confirm": true}'Response
{
"data": {
"success": true,
"message": "Agent removed from meeting"
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | confirmation_required | Neither dry_run nor confirm was sent |
| 404 | not_found | Meeting does not exist or is not in your workspace |
| 400 | invalid_request | Meeting not in progress / agent not in the call |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: preview_remove_agent_from_meeting, remove_agent_from_meeting
GET/v1/meetings/:meetingId/transcriptMeeting transcript
#get-v1-meetings-meetingid-transcript
Transcript, summary and action items; source = database, recall_ai or none.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
meetingId | path | string (uuid) | yes |
Request
curl -s "https://api.vendo-ai.com/v1/meetings/$MEETING_ID/transcript" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"id": "5d1e8f2a-3b4c-4d6e-8f9a-0b1c2d3e4f56",
"title": "Acme × Globex intro",
"messages": [],
"summary": null,
"action_items": [],
"source": "none"
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | Meeting does not exist or is not in your workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
Vendo SDR API 69
Signal-based prospecting: the New SDR wizard (company + website → ICP research), searches, the 24 signal hunters, contacts with ICP QA, outreach and search analytics.
The 24 signal hunters (ids for enabled_sources)
A new search enables all 24 hunters when enabled_sources is omitted (same as the web wizard). Hunters are not billed per run; every usable contact costs 5 credits. opt-in hunters are never switched on by platform defaults; paid engine means Vendo pays a search/AI provider; LinkedIn budget hunters share your accounts' daily LinkedIn budget. Live catalogue: GET /v1/sdr/hunters.
| Id | Name | Group | Where | How | Flags |
|---|---|---|---|---|---|
linkedin_jobs | LinkedIn hiring signals | LinkedIn job posts | Watches companies hiring for the roles that signal a need for your product, then finds the decision-makers at those companies. | LinkedIn budget | |
linkedin_company | LinkedIn company announcements | LinkedIn company pages | Reads company announcements (launches, new offices, expansion, partnerships) and treats them as timing signals. | LinkedIn budget | |
linkedin_activity | LinkedIn buying-intent posts | LinkedIn posts and comments | Runs your buyer-intent queries against public posts; a person writing about the pain you solve becomes a lead. | LinkedIn budget | |
ai_web_hunter | AI web hunter | web | The open web (engine A) | Searches the open web for dated company news that match your ICP (funding rounds, launches, expansion, new leaders) and keeps only first-party events. | opt-in, paid engine, daily cap |
social_signal_hunter | Social buying intent | web | LinkedIn, X, Reddit, Facebook groups, YouTube, Quora, Threads, Telegram, Hacker News, Stack Overflow | Reads public posts for first-person needs: tool requests, operational pains, switching, hiring and launches. The company named in the post becomes a signal; a LinkedIn author becomes a contact. | opt-in, paid engine, daily cap |
crunchbase_rss | Funding news | web | Funding news: 35+ startup and VC media feeds by region plus the news index in your countries' languages | Picks up companies that just raised a round, explodes weekly deal digests into every company listed, and saves the founder/CEO named in the announcement as a contact. | |
hn_hiring | Hacker News hiring | jobs | Hacker News: Who is hiring, Seeking freelancer, Show HN / Launch HN | Reads the monthly hiring and freelancer threads in full and every product launch of the last 7 days; the founder or hiring lead becomes a contact. | |
g2_reviews | Competitor switching | web | Competitor mentions: LinkedIn posts, X, Reddit, review sites | People saying in public that their company is leaving, evaluating, unhappy with or renewing a competitor product; the author becomes the contact. Competitors come from your ICP profile, research and competitor_pages. | daily cap |
tech_stack | Tech-stack changes | web | ATS job posts, competitor customer stories, website tool signatures | Finds companies that run a competitor or category tool: job posts that require the tool, new customer stories on the competitor's site, and tool signatures on the websites of companies already in your search. | daily cap |
connected_people | Decision-maker matching | internal | Inside every signal | For each company signal it finds the right decision-makers (names, titles, LinkedIn profiles), using decision_roles first. | internal step, LinkedIn budget |
email_enricher | Email discovery | internal | Email discovery and verification | Finds and verifies a business email for every contact (patterns, MX/SMTP checks) and drops contacts outside target_cities. | internal step |
linkedin_engagement | LinkedIn post engagement | Reactions and comments on LinkedIn | Collects the people who liked or commented on buying-intent posts in your niche. | opt-in, LinkedIn budget | |
linkedin_job_change | LinkedIn new-in-role | LinkedIn career updates | Finds people who started an ICP role in the last 90 days; new leaders re-evaluate vendors in their first months. | opt-in, LinkedIn budget | |
linkedin_competitor_followers | Competitor engagement | Reactions to competitor posts | Reads the recent posts of your competitors' LinkedIn pages and picks the decision-makers who react to them. | opt-in, LinkedIn budget | |
cross_pool | Shared signals | internal | Signals found for other searches | Reuses fresh matching signals discovered for other searches in Vendo; free and instant. | always on, not runnable |
people_pool | Contact pool | internal | The Vendo People database (700K+) | Matches your ICP against contacts Vendo already knows; the fastest first contacts on a new search. | always on, not runnable |
github_issues | GitHub buying intent | web | GitHub issues and pull requests | Developers at named organisations describing a need in the open. | opt-in |
adzuna_jobs | Job boards (19 countries) | jobs | Job boards in 19 countries (Adzuna) | Hiring intent outside LinkedIn: local job boards across Europe, the Americas and APAC. | opt-in |
corporate_registries | Executive appointments | data | Corporate registries (SEC 8-K Item 5.02 …) | Newly appointed executives from official filings; a new CxO is a strategic window for outreach. | opt-in |
web_news_radar | Web news radar | web | The open web (engine B) | The same ICP-driven news hunt on an independent search engine. Idle while the engine has no provider key. | opt-in, paid engine |
web_pulse_hunter | Web pulse hunter | web | The open web (engine C) | A third independent news engine on the same templates. Idle while the engine has no provider key. | opt-in, paid engine |
google_places | Local businesses (Google Places) | data | Google Maps business listings | For local-business ICPs: finds businesses by category and geography (salons, clinics, agencies, dealers) with phone and website. | opt-in, paid engine |
web_profile | Website profile | web | The company's own website | Reads the website of every company already in the search (agency, ad pixels, booking, stale blog, platform, open roles) for personalisation; named decision-makers on the team page become contacts. | opt-in |
local_listings | Local businesses (listings) | data | Local business listings database | For local-business ICPs: businesses by category and city with phone, website, rating and claimed status, including businesses without a website. Idle while the provider has no credentials. | opt-in, paid engine |
Overview & live signals 4
GET/v1/sdr/company-heatHot companies (one search or workspace-wide)
Companies ranked by live signal heat. With search_id: that search with total and offset paging (limit up to 500). Without: merged across the workspace's searches (first 500 rows: offset + limit ≤ 500), it used to return an arbitrary first search.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
search_id | query | uuid | One search | |
limit | query | integer | 1-500, default 50 | |
offset | query | integer | Paging offset |
Request
curl -s "https://api.vendo-ai.com/v1/sdr/company-heat" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"search_id": "9b1f0c3e-6a52-4d7e-9a43-2f6c1d8e7a10",
"count": 1,
"total": 23,
"offset": 0,
"companies": [
{
"company_id": "5a6b7c8d-9e0f-4a1b-8c2d-3e4f5a6b7c8d",
"company_name": "Northgate Dental",
"company_domain": "northgatedental.co.uk",
"heat": 82,
"icp_fit": "fit",
"signal_count": 3,
"signal_types": [
"hiring_role",
"news_mention"
],
"contact_status": "found",
"contacts_found": 2
}
]
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | A parameter is missing or invalid (message names it) |
| 404 | not_found | The search/contact/job does not exist or belongs to another workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: sdr_company_heat
GET/v1/sdr/learning-insightsSignal Learning insights
Learned weight per signal type and conversion from real outreach outcomes. With search_id: that search. Without: one entry per search of the workspace (it used to return an arbitrary first search).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
search_id | query | uuid | One search |
Request
curl -s "https://api.vendo-ai.com/v1/sdr/learning-insights" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"search_id": "9b1f0c3e-6a52-4d7e-9a43-2f6c1d8e7a10",
"weights": [
{
"signal_type": "hiring_role",
"weight": 1.32,
"samples": 140
}
],
"conversion": [
{
"signal_type": "hiring_role",
"sent": 140,
"replied": 6,
"meetings": 2
}
]
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | A parameter is missing or invalid (message names it) |
| 404 | not_found | The search/contact/job does not exist or belongs to another workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
GET/v1/sdr/overviewAll SDR processes with health numbers
The /vendo-sdr list page: every search of the workspace with signals (7 days, previous 7, 30 days), contacts (total, with email, new in 7 days), hot companies, hunters enabled/running/cooling/paused, outreach delivery and a daily series, plus workspace totals. Read-only.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
days | query | integer | Daily series length 7-60, default 14 |
Request
curl -s "https://api.vendo-ai.com/v1/sdr/overview" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Show the full response (46 lines)
{
"data": {
"workspace_id": "0d9e8f7a-6b5c-4d3e-2f1a-0b9c8d7e6f5a",
"days": [
"2026-10-13",
"…",
"2026-10-26"
],
"totals": {
"searches": 2,
"active": 2,
"signals_7d": 184,
"signals_prev7d": 151,
"signals_30d": 702,
"contacts_total": 431,
"contacts_email": 318,
"contacts_new7d": 57,
"hot_companies": 23,
"hunters_running": 31,
"hunters_enabled": 46,
"daily": [
12,
15,
9
]
},
"searches": [
{
"id": "9b1f0c3e-6a52-4d7e-9a43-2f6c1d8e7a10",
"name": "UK dental clinics — buyers",
"status": "active",
"signals_7d": 120,
"contacts_total": 260,
"hunters_enabled": 24,
"hunters_running": 18,
"hunters_cooling": 2,
"hunters_paused": 0,
"daily": [
8,
10,
6
]
}
]
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | A parameter is missing or invalid (message names it) |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: sdr_overview
GET/v1/sdr/recentRecent signals (one search or workspace-wide)
Latest signals. With search_id: that search. Without: merged across the workspace's searches (newest first, each row tagged with search_id/search_name), it used to return an arbitrary first search.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
search_id | query | uuid | One search | |
limit | query | integer | 1-100, default 20 |
Request
curl -s "https://api.vendo-ai.com/v1/sdr/recent" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"search_id": null,
"searches_scanned": 2,
"signals": [
{
"id": "3a4b5c6d-7e8f-4091-a2b3-c4d5e6f7a8b9",
"search_id": "9b1f0c3e-6a52-4d7e-9a43-2f6c1d8e7a10",
"search_name": "UK dental clinics — buyers",
"source": "linkedin_jobs",
"signal_type": "hiring_role",
"company_name": "Northgate Dental",
"topic": "Practice Manager",
"detected_at": "2026-10-26T10:58:00Z"
}
]
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | A parameter is missing or invalid (message names it) |
| 404 | not_found | The search/contact/job does not exist or belongs to another workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: sdr_recent_signals
Wizard & ICP research 9
GET/v1/sdr/agents/:id/profileGet the agent's current signal profile
Read the agent's current signal profile (ICP, offer summary, hunter weights). Returns { exists: false } when nothing's generated yet.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string | yes |
Request
curl -s "https://api.vendo-ai.com/v1/sdr/agents/$AGENT_ID/profile" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
POST/v1/sdr/agents/:id/profile/generate(Re-)generate the signal profile (Claude call)
#post-v1-sdr-agents-id-profile-generate
Run Claude on the agent's knowledge to (re)generate its signal profile. Persists onto the agent so later Signal Searches reuse it.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string | yes |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/sdr/agents/$AGENT_ID/profile/generate" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
GET/v1/sdr/geo/citiesFind cities for target_cities
The Regions step city picker. Without q it returns the most populous cities per country/state (default 12, max 30 each); with q (2+ Latin letters) it searches names inside those countries (default 20, max 50). Use the objects as target_cities. Static GeoNames data (places of 5 000+ people), no side effects.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
countries | query | string | yes | Comma-separated ISO-2 or CC:SUB codes, max 40 (sub-national filter applies to US states only) |
q | query | string | Name search, 2-80 characters | |
limit | query | integer | Per country without q (1-30), total with q (1-50) |
Request
curl -s "https://api.vendo-ai.com/v1/sdr/geo/cities?countries=$COUNTRIES" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"cities": [
{
"country": "GB",
"name": "London",
"admin1": "England",
"admin1_code": "ENG",
"population": 8961989,
"lat": 51.50853,
"lng": -0.12574
},
{
"country": "GB",
"name": "Manchester",
"admin1": "England",
"admin1_code": "ENG",
"population": 395515,
"lat": 53.48095,
"lng": -2.23743
}
]
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | A parameter is missing or invalid (message names it) |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: sdr_find_cities
GET/v1/sdr/researchSetup state and latest research of a company profile
Poll this after POST /v1/sdr/research while the company profile is being built (setup.status running → done / error), and to read the latest ICP research of a company profile. setup is null when no background build ran in the last 24 h. Read-only.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
agent_id | query | uuid | yes | Company profile id returned by POST /v1/sdr/research |
Request
curl -s "https://api.vendo-ai.com/v1/sdr/research?agent_id=$AGENT_ID" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Show the full response (58 lines)
{
"data": {
"agent_id": "4c2e8a71-3b9d-4f60-8e15-7d0a9c6b2e44",
"setup": {
"status": "done",
"phase": "research",
"message": "Company profile ready; ICP research started.",
"job_id": "e7d41b2a-0c58-4a9e-b6f3-91a2d4c8e5f7"
},
"job": {
"id": "e7d41b2a-0c58-4a9e-b6f3-91a2d4c8e5f7",
"agent_id": "4c2e8a71-3b9d-4f60-8e15-7d0a9c6b2e44",
"company_name": "Brightsmile Software",
"website": "https://brightsmile.io",
"status": "running",
"current_stage": "web",
"stages": [
{
"key": "profile",
"status": "done"
},
{
"key": "web",
"status": "running"
}
],
"result": null,
"error": null,
"instant_audience_summary": null
},
"stages": [
{
"key": "profile",
"title": "Reading your website"
},
{
"key": "web",
"title": "Researching your market on the web"
},
{
"key": "icp",
"title": "Building your ideal customer profile"
},
{
"key": "qa",
"title": "Checking the profile for consistency"
},
{
"key": "save",
"title": "Preparing your hunting plan"
},
{
"key": "audience",
"title": "Finding your first buyers in our database"
}
]
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | A parameter is missing or invalid (message names it) |
| 404 | not_found | The search/contact/job does not exist or belongs to another workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: sdr_get_research
POST/v1/sdr/researchStart the New SDR wizard for a company website
Company + website step of /vendo-sdr/new, run server-side. Reuses the workspace's company profile for the same domain, or builds a hidden one exactly like the web app (create profile, website deep scan 15 credits, save scan data, outreach playbook 1 credit, activate) in the background; then runs the ICP research (or reuses one under 24 h old unless force). Hidden profiles never appear in /v1/agents and do not count toward the agent limit. dry_run shows reuse and cost; confirm starts. At most 5 new company profiles per API key per hour.
Credits: 16 credits when a new company profile is built (deep scan 15 + playbook 1); 0 when reused · Protected: send {"dry_run": true} to preview, then {"confirm": true} to execute (protocol).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
company_name | body | string | yes | Company name |
website | body | string | yes | Domain or URL, e.g. acme.com |
force | body | boolean | Run a fresh research even if one under 24 h exists | |
dry_run | body | boolean | Preview only | |
confirm | body | boolean | Execute |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/sdr/research" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"company_name": "Brightsmile Software", "website": "brightsmile.io", "confirm": true}'Response
{
"data": {
"agent_id": "4c2e8a71-3b9d-4f60-8e15-7d0a9c6b2e44",
"reused_company_profile": false,
"setup": {
"agent_id": "4c2e8a71-3b9d-4f60-8e15-7d0a9c6b2e44",
"company_name": "Brightsmile Software",
"website": "https://brightsmile.io",
"domain": "brightsmile.io",
"status": "running",
"phase": "deep_scan",
"message": "Reading your website…",
"error": null,
"job_id": null,
"started_at": "2026-10-26T10:02:11.000Z",
"finished_at": null,
"phases": [
"company",
"deep_scan",
"save_scan",
"script",
"activate",
"research"
]
},
"job": null,
"next": "GET /v1/sdr/research?agent_id=4c2e8a71-3b9d-4f60-8e15-7d0a9c6b2e44 every 5-10 s until setup.status is done (the research job then appears) or error."
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | A parameter is missing or invalid (message names it) |
| 400 | confirmation_required | Neither dry_run nor confirm was sent |
| 400 | forbidden_field | The body carries a field that cannot be set here |
| 400 | invalid_request | Insufficient credits (agent creation needs ≥1, deep scan 15) |
| 429 | rate_limited | More than 5 new company profiles from this key in an hour |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
GET/v1/sdr/research/:idPoll one ICP research job
Replaces the research SSE stream: poll every 2-5 s until status is done or error. When done, result holds the company dossier and the editable profile (search_name, offer_summary, icp_summary, buyer_type, industry_keywords, decision_roles, company_sizes, target_regions, region_reasoning, languages, pains, buying_triggers, relevant_topics, signal_weights, exclude_keywords, linkedin_search_queries, competitor_pages, hunter_plan, reasoning), QA notes, pages read and web sources. The research itself is not billed in credits.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | Research job id |
Request
curl -s "https://api.vendo-ai.com/v1/sdr/research/$RESEARCH_JOB_ID" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Show the full response (85 lines)
{
"data": {
"job": {
"id": "e7d41b2a-0c58-4a9e-b6f3-91a2d4c8e5f7",
"agent_id": "4c2e8a71-3b9d-4f60-8e15-7d0a9c6b2e44",
"status": "done",
"current_stage": null,
"stages": [
{
"key": "profile",
"status": "done",
"started_at": "2026-10-26T10:05:01Z",
"finished_at": "2026-10-26T10:05:14Z"
}
],
"events": [
{
"ts": "2026-10-26T10:05:20Z",
"type": "log",
"stage": "web",
"message": "Reading competitor pricing pages"
}
],
"result": {
"company": {
"name": "Brightsmile Software",
"hq_country": "GB"
},
"profile": {
"search_name": "UK dental clinics — buyers",
"offer_summary": "Online booking and recall automation for dental clinics.",
"icp_summary": "Independent dental clinics with 2–10 chairs.",
"decision_roles": [
"Practice Owner",
"Practice Manager"
],
"company_sizes": [
"11–50"
],
"target_regions": [
"GB"
],
"languages": [
"English"
],
"industry_keywords": [
"dental clinic"
],
"competitor_pages": [
"https://www.linkedin.com/company/dentally/"
],
"hunter_plan": [
{
"source": "linkedin_jobs",
"why": "Clinics hiring practice managers"
}
]
},
"qa": {
"ok": true,
"issues": [],
"applied": []
},
"pages_read": [
{
"url": "https://brightsmile.io/",
"title": "Brightsmile"
}
],
"web_sources": [],
"generated_at": "2026-10-26T10:07:40Z"
},
"error": null,
"instant_audience_summary": {
"total": 1840,
"shown": 20,
"with_email": 14,
"with_photo": 17,
"generated_at": "2026-10-26T10:08:30Z",
"materialized": null
}
},
"stages": []
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | The search/contact/job does not exist or belongs to another workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: sdr_get_research
GET/v1/sdr/research/:id/audienceFirst buyers found by a research
#get-v1-sdr-research-id-audience
The review step's "buyers already found" cards: up to ~20 people from the Vendo People database matching the researched ICP, with company, position, country, LinkedIn, email (shown in full, as in the UI) and an AI fit explanation. Cached on the job; refresh=1 rebuilds it (up to ~60 s). Read-only, no credits.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | Research job id |
refresh | query | 0|1 | Rebuild the audience |
Request
curl -s "https://api.vendo-ai.com/v1/sdr/research/$RESEARCH_JOB_ID/audience" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Show the full response (49 lines)
{
"data": {
"people": [
{
"id": "pp_88123",
"name": "Sarah Collins",
"position": "Practice Manager",
"company": "Northgate Dental",
"country": "United Kingdom",
"city": "Leeds",
"industry": "Dental",
"avatar_url": null,
"linkedin_url": "https://www.linkedin.com/in/sarah-collins-dental",
"has_email": true,
"email": "sarah@northgatedental.co.uk",
"website": "https://northgatedental.co.uk",
"fit": {
"role": "Practice Manager",
"industry": "dental",
"region": "GB"
},
"ai": {
"company_summary": "Three-chair family practice.",
"fit_reason": "Runs operations and booking at an independent clinic.",
"fit_score": 86,
"matched": {
"role": true,
"industry": "dental",
"region": true
}
}
}
],
"total": 1840,
"with_photo": 17,
"with_email": 14,
"countries": [
"United Kingdom"
],
"positions": [
"Practice Manager"
],
"industries": [
"Dental"
],
"took_ms": 4210,
"generated_at": "2026-10-26T10:08:30Z"
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | The search/contact/job does not exist or belongs to another workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
POST/v1/sdr/research/:id/materializeCopy a research's first buyers into a search
#post-v1-sdr-research-id-materialize
Copies the instant audience into the search as contacts in the background (idempotent, filtered by the search's regions and cities). POST /v1/sdr/searches with research_job_id does this automatically after activation. Contacts are billed like any other contact (5 credits) when used.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | Research job id |
search_id | body | uuid | yes | Target search of this workspace |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/sdr/research/$RESEARCH_JOB_ID/materialize" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"search_id": "9b1f0c3e-6a52-4d7e-9a43-2f6c1d8e7a10"}'Response
{
"data": {
"ok": true,
"started": true
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | A parameter is missing or invalid (message names it) |
| 404 | not_found | The search/contact/job does not exist or belongs to another workspace |
| 400 | forbidden_field | The body carries a field that cannot be set here |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
POST/v1/sdr/searches/previewPreview a search profile (no save)
Generates the signal-search profile (ICP, keywords, hunters) the app would propose for an agent, without saving anything. Runs one AI call that takes 30–90 s (no credits). Fixed (2026-10-26): the gateway waited only 30 s and then crashed with a raw 500; it now waits up to 180 s and a timeout answers 504 timeout.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
agent_id | body | uuid | yes | One of your agents (GET /v1/agents) |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/sdr/searches/preview" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"agent_id": "a91d4f60-2c3b-4e7a-8f15-0b6c9d2e4a71"}'Response
{
"data": {
"name": "UK dental clinics hiring front-desk staff",
"icp": {
"industries": [
"Dental clinics"
],
"locations": [
"United Kingdom"
],
"company_size": "2-50",
"roles": [
"Practice owner",
"Practice manager"
]
},
"keywords": [
"dental receptionist",
"practice manager"
],
"enabled_sources": [
"linkedin_jobs",
"google_places",
"web_news_radar"
]
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | agent_id missing |
| 404 | not_found | Agent does not exist or is not in your workspace |
| 504 | timeout | The AI call took longer than 180 s; retry |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: sdr_preview_search
Searches 9
GET/v1/sdr/searchesList all signal searches
List active Signal Searches in this workspace. Optionally filter by agent.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
agent_id | query | string |
Request
curl -s "https://api.vendo-ai.com/v1/sdr/searches" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: sdr_list_searches
POST/v1/sdr/searchesCreate an SDR search (Create & Start)
Creates a search the way the wizard's "Create & Start" does. With research_job_id every field defaults to the research profile (name, regions or HQ country, offer, ICP + decision-makers + languages + company size, keywords, LinkedIn queries, competitor pages, all 24 hunters), competitor pages are saved, the search is activated (start defaults to true) and the research's first buyers are copied in. Legacy: agent_id + name + target_regions; when no offer/ICP is given the profile is generated from the company profile. Contacts cost 5 credits each once hunting starts.
Credits: No charge to create; 5 credits per usable contact once hunting
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
research_job_id | body | uuid | Finished research job (recommended) | |
agent_id | body | uuid | Company profile id (legacy path; must match the research's if both are given) | |
name | body | string | Process name (max 200) | |
enabled_sources | body | string[] | Hunters to run (GET /v1/sdr/hunters). At least one one of: linkedin_jobs, linkedin_company, linkedin_activity, ai_web_hunter, social_signal_hunter, crunchbase_rss, hn_hiring, g2_reviews, tech_stack, connected_people, email_enricher, linkedin_engagement, linkedin_job_change, linkedin_competitor_followers, cross_pool, people_pool, github_issues, adzuna_jobs, corporate_registries, web_news_radar, web_pulse_hunter, google_places, web_profile, local_listings | |
target_regions | body | string[] | ISO-2 countries or US states as CC:SUB (e.g. GB, US:FL). At least one | |
target_cities | body | object[] | Up to 100 {country, name, admin1?, admin1_code?, lat?, lng?} inside target_regions (GET /v1/sdr/geo/cities). Cities of removed countries are dropped; empty = whole countries | |
offer_summary | body | string | "What you sell" (max 10 000) | |
icp_summary | body | string | "Who buys". The "Decision-makers: …." sentence and the "Preferred company size: …" line are rebuilt from decision_roles and company_size, exactly as the web wizard does | |
decision_roles | body | string[] | Decision-maker job titles (max 20); also stored on their own field | |
company_size | body | string|null | e.g. "11–50 employees"; null removes the size line | |
industry_keywords | body | string[] | Industries / keywords | |
exclude_keywords | body | string[] | Exclusions | |
linkedin_search_queries | body | object[] | Up to 50 {query, location_hint?, rationale?} | |
competitor_pages | body | string[] | Competitor LinkedIn page URLs (Competitor Engagement / Competitor Switching hunters) | |
relevant_topics | body | object | {signal_type: string[]} | |
signal_weights | body | object | {signal_type: number 0-10} | |
languages | body | string[] | Outreach languages, written into icp_summary | |
generation_reasoning | body | string | Free-text reasoning kept with the profile | |
generation_model | body | string | Label of what generated the profile | |
start | body | boolean | Activate hunters right away (default true) |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/sdr/searches" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"research_job_id": "e7d41b2a-0c58-4a9e-b6f3-91a2d4c8e5f7",
"target_regions": [
"GB"
],
"target_cities": [
{
"country": "GB",
"name": "Manchester"
}
],
"company_size": "11–50 employees"
}'Response
Show the full response (86 lines)
{
"data": {
"search": {
"id": "9b1f0c3e-6a52-4d7e-9a43-2f6c1d8e7a10",
"agent_id": "4c2e8a71-3b9d-4f60-8e15-7d0a9c6b2e44",
"name": "UK dental clinics — buyers",
"status": "active",
"enabled_sources": [
"linkedin_jobs",
"linkedin_company",
"linkedin_activity",
"ai_web_hunter",
"social_signal_hunter",
"crunchbase_rss",
"hn_hiring",
"g2_reviews",
"tech_stack",
"connected_people",
"email_enricher",
"linkedin_engagement",
"linkedin_job_change",
"linkedin_competitor_followers",
"cross_pool",
"people_pool",
"github_issues",
"adzuna_jobs",
"corporate_registries",
"web_news_radar",
"web_pulse_hunter",
"google_places",
"web_profile",
"local_listings"
],
"offer_summary": "Online booking and recall automation for dental clinics.",
"icp_summary": "Independent dental clinics with 2–10 chairs planning to grow patient volume. Decision-makers: Practice Owner, Practice Manager.\nPreferred company size: 11–50 employees.",
"decision_roles": [
"Practice Owner",
"Practice Manager"
],
"industry_keywords": [
"dental clinic",
"dentist"
],
"exclude_keywords": [
"dental lab"
],
"linkedin_search_queries": [
{
"query": "practice manager dental",
"location_hint": "United Kingdom"
}
],
"competitor_pages": [
"https://www.linkedin.com/company/dentally/"
],
"target_regions": [
"GB"
],
"target_cities": [
{
"country": "GB",
"name": "Manchester",
"admin1": "England",
"lat": 53.48,
"lng": -2.24
}
],
"hot_threshold": 70,
"human_edited": true,
"created_at": "2026-10-26T09:12:44.000Z"
},
"activated": true,
"started": [
"linkedin_jobs",
"linkedin_company",
"connected_people",
"email_enricher",
"people_pool"
],
"instant_audience": {
"ok": true,
"started": true
},
"warnings": []
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | A parameter is missing or invalid (message names it) |
| 400 | forbidden_field | The body carries a field that cannot be set here |
| 404 | not_found | The search/contact/job does not exist or belongs to another workspace |
| 409 | research_not_ready | The research job is not done yet |
| 409 | conflict | A live search with the same name, or an identical one created minutes ago, exists for this company profile |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: sdr_create_search
GET/v1/sdr/searches/:idGet search details
Fetch one Signal Search by id, including its profile, hunters, and contact counts.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string | yes |
Request
curl -s "https://api.vendo-ai.com/v1/sdr/searches/$SEARCH_ID" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: sdr_get_search
PATCH/v1/sdr/searches/:idEdit an SDR search (wizard Save changes)
Explicit allow-list of the editable profile fields; status, agent_id, custom-planning and internal flags are rejected with forbidden_field. Hunter ids are validated against the catalogue. icp_summary, decision_roles and company_size are recombined exactly as the edit wizard does. Status is not touched: an active search keeps hunting with the new profile.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | SDR search (process) id |
name | body | string | Process name (max 200) | |
enabled_sources | body | string[] | Hunters to run (GET /v1/sdr/hunters). At least one one of: linkedin_jobs, linkedin_company, linkedin_activity, ai_web_hunter, social_signal_hunter, crunchbase_rss, hn_hiring, g2_reviews, tech_stack, connected_people, email_enricher, linkedin_engagement, linkedin_job_change, linkedin_competitor_followers, cross_pool, people_pool, github_issues, adzuna_jobs, corporate_registries, web_news_radar, web_pulse_hunter, google_places, web_profile, local_listings | |
target_regions | body | string[] | ISO-2 countries or US states as CC:SUB (e.g. GB, US:FL). At least one | |
target_cities | body | object[] | Up to 100 {country, name, admin1?, admin1_code?, lat?, lng?} inside target_regions (GET /v1/sdr/geo/cities). Cities of removed countries are dropped; empty = whole countries | |
offer_summary | body | string | "What you sell" (max 10 000) | |
icp_summary | body | string | "Who buys". The "Decision-makers: …." sentence and the "Preferred company size: …" line are rebuilt from decision_roles and company_size, exactly as the web wizard does | |
decision_roles | body | string[] | Decision-maker job titles (max 20); also stored on their own field | |
company_size | body | string|null | e.g. "11–50 employees"; null removes the size line | |
industry_keywords | body | string[] | Industries / keywords | |
exclude_keywords | body | string[] | Exclusions | |
linkedin_search_queries | body | object[] | Up to 50 {query, location_hint?, rationale?} | |
competitor_pages | body | string[] | Competitor LinkedIn page URLs (Competitor Engagement / Competitor Switching hunters) | |
relevant_topics | body | object | {signal_type: string[]} | |
signal_weights | body | object | {signal_type: number 0-10} | |
hot_threshold | body | integer | Hot company threshold 0-100 (default 70) |
Request
curl -s -X PATCH "https://api.vendo-ai.com/v1/sdr/searches/$SEARCH_ID" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"decision_roles": [
"Practice Owner",
"Clinical Director"
],
"target_cities": [],
"enabled_sources": [
"linkedin_jobs",
"connected_people",
"email_enricher",
"people_pool",
"cross_pool"
]
}'Response
Show the full response (74 lines)
{
"data": {
"ok": true,
"search": {
"id": "9b1f0c3e-6a52-4d7e-9a43-2f6c1d8e7a10",
"agent_id": "4c2e8a71-3b9d-4f60-8e15-7d0a9c6b2e44",
"name": "UK dental clinics — buyers",
"status": "active",
"enabled_sources": [
"linkedin_jobs",
"linkedin_company",
"linkedin_activity",
"ai_web_hunter",
"social_signal_hunter",
"crunchbase_rss",
"hn_hiring",
"g2_reviews",
"tech_stack",
"connected_people",
"email_enricher",
"linkedin_engagement",
"linkedin_job_change",
"linkedin_competitor_followers",
"cross_pool",
"people_pool",
"github_issues",
"adzuna_jobs",
"corporate_registries",
"web_news_radar",
"web_pulse_hunter",
"google_places",
"web_profile",
"local_listings"
],
"offer_summary": "Online booking and recall automation for dental clinics.",
"icp_summary": "Independent dental clinics with 2–10 chairs planning to grow patient volume. Decision-makers: Practice Owner, Practice Manager.\nPreferred company size: 11–50 employees.",
"decision_roles": [
"Practice Owner",
"Practice Manager"
],
"industry_keywords": [
"dental clinic",
"dentist"
],
"exclude_keywords": [
"dental lab"
],
"linkedin_search_queries": [
{
"query": "practice manager dental",
"location_hint": "United Kingdom"
}
],
"competitor_pages": [
"https://www.linkedin.com/company/dentally/"
],
"target_regions": [
"GB"
],
"target_cities": [
{
"country": "GB",
"name": "Manchester",
"admin1": "England",
"lat": 53.48,
"lng": -2.24
}
],
"hot_threshold": 70,
"human_edited": true,
"created_at": "2026-10-26T09:12:44.000Z"
}
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | A parameter is missing or invalid (message names it) |
| 400 | forbidden_field | The body carries a field that cannot be set here |
| 404 | not_found | The search/contact/job does not exist or belongs to another workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: sdr_update_search
DELETE/v1/sdr/searches/:idDelete an SDR process and all its data
Irreversible: hunters stop, the outreach campaign is stopped and deleted with its recipients and events, and every signal, contact, hunter state, ICP-QA run, outcome and learning weight of the search is removed. dry_run now reports reachable contacts, signals of the last 90 days and the outreach campaign status. Exported audiences are kept; no refund.
Protected: send {"dry_run": true} to preview, then {"confirm": true} to execute (protocol).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | SDR search (process) id |
dry_run | body | boolean | Preview | |
confirm | body | boolean | Execute |
Request
curl -s -X DELETE "https://api.vendo-ai.com/v1/sdr/searches/$SEARCH_ID" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"dry_run": true}'Response
{
"data": {
"dry_run": true,
"would_do": "Delete the SDR process \"UK dental clinics — buyers\" and ALL its data, irreversibly: …",
"search": {
"id": "9b1f0c3e-6a52-4d7e-9a43-2f6c1d8e7a10",
"name": "UK dental clinics — buyers",
"status": "active"
},
"estimated": {
"contacts_reachable": 260,
"signals_last_90_days": 702,
"outreach_campaign": {
"status": "active",
"channels": [
"email",
"linkedin"
]
}
},
"reversible": false,
"note": "Credits already spent on contacts are not refunded. Custom audiences you exported earlier are kept."
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | confirmation_required | Neither dry_run nor confirm was sent |
| 404 | not_found | The search/contact/job does not exist or belongs to another workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
POST/v1/sdr/searches/:id/activateActivate a search
#post-v1-sdr-searches-id-activate
Set status=active and fire-start all enabled hunters. Reversible via sdr_pause_search.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string | yes |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/sdr/searches/$SEARCH_ID/activate" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: sdr_activate_search
POST/v1/sdr/searches/:id/pausePause an SDR process
#post-v1-sdr-searches-id-pause
Pauses the whole process: every hunter stops AND outreach sending stops (email campaign paused, no LinkedIn sends). Data is kept. Route unchanged; documented semantics corrected (it used to say hunters only).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | SDR search (process) id |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/sdr/searches/$SEARCH_ID/pause" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Show the full response (75 lines)
{
"data": {
"ok": true,
"search": {
"id": "9b1f0c3e-6a52-4d7e-9a43-2f6c1d8e7a10",
"agent_id": "4c2e8a71-3b9d-4f60-8e15-7d0a9c6b2e44",
"name": "UK dental clinics — buyers",
"status": "paused",
"enabled_sources": [
"linkedin_jobs",
"linkedin_company",
"linkedin_activity",
"ai_web_hunter",
"social_signal_hunter",
"crunchbase_rss",
"hn_hiring",
"g2_reviews",
"tech_stack",
"connected_people",
"email_enricher",
"linkedin_engagement",
"linkedin_job_change",
"linkedin_competitor_followers",
"cross_pool",
"people_pool",
"github_issues",
"adzuna_jobs",
"corporate_registries",
"web_news_radar",
"web_pulse_hunter",
"google_places",
"web_profile",
"local_listings"
],
"offer_summary": "Online booking and recall automation for dental clinics.",
"icp_summary": "Independent dental clinics with 2–10 chairs planning to grow patient volume. Decision-makers: Practice Owner, Practice Manager.\nPreferred company size: 11–50 employees.",
"decision_roles": [
"Practice Owner",
"Practice Manager"
],
"industry_keywords": [
"dental clinic",
"dentist"
],
"exclude_keywords": [
"dental lab"
],
"linkedin_search_queries": [
{
"query": "practice manager dental",
"location_hint": "United Kingdom"
}
],
"competitor_pages": [
"https://www.linkedin.com/company/dentally/"
],
"target_regions": [
"GB"
],
"target_cities": [
{
"country": "GB",
"name": "Manchester",
"admin1": "England",
"lat": 53.48,
"lng": -2.24
}
],
"hot_threshold": 70,
"human_edited": true,
"created_at": "2026-10-26T09:12:44.000Z"
},
"outreachPaused": true
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | The search/contact/job does not exist or belongs to another workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: sdr_pause_search
POST/v1/sdr/searches/:id/regenerateRegenerate search configuration
#post-v1-sdr-searches-id-regenerate
Re-run Claude on the agent's current knowledge to regenerate the entire ICP profile and hunter configuration. Useful after the agent's script changed significantly.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string | yes |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/sdr/searches/$SEARCH_ID/regenerate" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
POST/v1/sdr/searches/:id/resumeResume an SDR process
#post-v1-sdr-searches-id-resume
Hunters restart and outreach that was paused together with the search resumes; outreach you paused separately stays paused. Route unchanged; documented semantics corrected.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | SDR search (process) id |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/sdr/searches/$SEARCH_ID/resume" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Show the full response (79 lines)
{
"data": {
"ok": true,
"search": {
"id": "9b1f0c3e-6a52-4d7e-9a43-2f6c1d8e7a10",
"agent_id": "4c2e8a71-3b9d-4f60-8e15-7d0a9c6b2e44",
"name": "UK dental clinics — buyers",
"status": "active",
"enabled_sources": [
"linkedin_jobs",
"linkedin_company",
"linkedin_activity",
"ai_web_hunter",
"social_signal_hunter",
"crunchbase_rss",
"hn_hiring",
"g2_reviews",
"tech_stack",
"connected_people",
"email_enricher",
"linkedin_engagement",
"linkedin_job_change",
"linkedin_competitor_followers",
"cross_pool",
"people_pool",
"github_issues",
"adzuna_jobs",
"corporate_registries",
"web_news_radar",
"web_pulse_hunter",
"google_places",
"web_profile",
"local_listings"
],
"offer_summary": "Online booking and recall automation for dental clinics.",
"icp_summary": "Independent dental clinics with 2–10 chairs planning to grow patient volume. Decision-makers: Practice Owner, Practice Manager.\nPreferred company size: 11–50 employees.",
"decision_roles": [
"Practice Owner",
"Practice Manager"
],
"industry_keywords": [
"dental clinic",
"dentist"
],
"exclude_keywords": [
"dental lab"
],
"linkedin_search_queries": [
{
"query": "practice manager dental",
"location_hint": "United Kingdom"
}
],
"competitor_pages": [
"https://www.linkedin.com/company/dentally/"
],
"target_regions": [
"GB"
],
"target_cities": [
{
"country": "GB",
"name": "Manchester",
"admin1": "England",
"lat": 53.48,
"lng": -2.24
}
],
"hot_threshold": 70,
"human_edited": true,
"created_at": "2026-10-26T09:12:44.000Z"
},
"started": [
"linkedin_jobs",
"connected_people"
],
"outreachResumed": true
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | The search/contact/job does not exist or belongs to another workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: sdr_resume_search
Hunters 12
GET/v1/sdr/huntersHunter catalogue (24 sources)
Every hunter source a search can run, rebuilt from the current backend: UI name and dashboard station, where it looks and how, opt_in_only (never switched on by platform defaults), budget_throttled (daily cycle cap), paid_engine (Vendo pays a search/AI provider), uses_linkedin_budget, internal/always_on, runnable (Run button) and the search fields it reads. The web wizard and POST /v1/sdr/searches enable all 24 by default. Hunters are not billed per run; each usable contact costs 5 credits.
Parameters
No parameters.
Request
curl -s "https://api.vendo-ai.com/v1/sdr/hunters" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Show the full response (36 lines)
{
"data": {
"hunters": [
{
"id": "linkedin_competitor_followers",
"name": "Competitor engagement",
"station": "Competitor Engagement Hunter",
"group": "linkedin",
"where": "Reactions to competitor posts",
"how": "Reads the recent posts of your competitors' LinkedIn pages and picks the decision-makers who react to them.",
"opt_in_only": true,
"budget_throttled": false,
"paid_engine": false,
"uses_linkedin_budget": true,
"internal": false,
"always_on": false,
"runnable": true,
"inputs": [
"competitor_pages",
"target_regions"
],
"requires": "competitor_pages (LinkedIn company page URLs) or competitors found by the ICP research"
}
],
"count": 24,
"ui_default": "The web wizard enables all 24 hunters on a new search; POST /v1/sdr/searches does the same when enabled_sources is omitted.",
"not_a_source": [
{
"id": "static_base",
"name": "Static Base Hunter",
"note": "Runs automatically every ~8 minutes on every active search."
}
],
"cost": "Hunters are not billed per run. Every usable contact a search delivers costs 5 credits (charged once per contact); contacts the ICP check marks Not a fit are free."
}
}Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: sdr_list_hunters
GET/v1/sdr/searches/:id/healthPer-search operator dashboard
#get-v1-sdr-searches-id-health
Operator-level health view: states, signals/contacts produced, credits spent, recent events, learning insights for a single search.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string | yes |
Request
curl -s "https://api.vendo-ai.com/v1/sdr/searches/$SEARCH_ID/health" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: sdr_health
GET/v1/sdr/searches/:id/huntersHunter stations of a search
#get-v1-sdr-searches-id-hunters
Every catalogue hunter for one search: enabled flag, live run state, signals in the last 30 days, a 7-day sparkline and the current values of the search fields it reads. static_base is reported separately. Read-only.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | SDR search (process) id |
Request
curl -s "https://api.vendo-ai.com/v1/sdr/searches/$SEARCH_ID/hunters" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Show the full response (62 lines)
{
"data": {
"search_id": "9b1f0c3e-6a52-4d7e-9a43-2f6c1d8e7a10",
"search_status": "active",
"enabled_sources": [
"linkedin_jobs",
"connected_people"
],
"hunters": [
{
"id": "linkedin_jobs",
"name": "LinkedIn hiring signals",
"station": "LinkedIn Hunter",
"group": "linkedin",
"enabled": true,
"state": {
"status": "running",
"paused": false,
"phase": "searching",
"current_action": "practice manager dental"
},
"signals_30d": 182,
"daily_7d": [
4,
6,
3,
8,
5,
7,
2
],
"paid_engine": false,
"opt_in_only": false,
"internal": false,
"always_on": false,
"runnable": true,
"settings": {
"linkedin_search_queries": [
{
"query": "practice manager dental"
}
],
"target_regions": [
"GB"
]
},
"requires": null
}
],
"static_base": {
"source": "static_base",
"status": "running",
"paused": false,
"note": "Runs automatically every ~8 min"
},
"days": [
"2026-10-20",
"…"
],
"note": "A hunter enabled on an active search starts within ~10 minutes (budget-throttled ones within ~30). A disabled hunter stops at its next cycle; pause it to stop at once."
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | The search/contact/job does not exist or belongs to another workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: sdr_search_hunters
PATCH/v1/sdr/searches/:id/huntersSwitch hunters on/off
#patch-v1-sdr-searches-id-hunters
The wizard's Hunters step for an existing search. Pass enable/disable lists or enabled_sources (full list, not both). On an active search added hunters start within ~10 minutes (budget-throttled ones ~30); removed hunters stop at their next cycle. At least one hunter must stay on.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | SDR search (process) id |
enable | body | string[] | Sources to switch on one of: linkedin_jobs, linkedin_company, linkedin_activity, ai_web_hunter, social_signal_hunter, crunchbase_rss, hn_hiring, g2_reviews, tech_stack, connected_people, email_enricher, linkedin_engagement, linkedin_job_change, linkedin_competitor_followers, cross_pool, people_pool, github_issues, adzuna_jobs, corporate_registries, web_news_radar, web_pulse_hunter, google_places, web_profile, local_listings | |
disable | body | string[] | Sources to switch off one of: linkedin_jobs, linkedin_company, linkedin_activity, ai_web_hunter, social_signal_hunter, crunchbase_rss, hn_hiring, g2_reviews, tech_stack, connected_people, email_enricher, linkedin_engagement, linkedin_job_change, linkedin_competitor_followers, cross_pool, people_pool, github_issues, adzuna_jobs, corporate_registries, web_news_radar, web_pulse_hunter, google_places, web_profile, local_listings | |
enabled_sources | body | string[] | Full replacement list one of: linkedin_jobs, linkedin_company, linkedin_activity, ai_web_hunter, social_signal_hunter, crunchbase_rss, hn_hiring, g2_reviews, tech_stack, connected_people, email_enricher, linkedin_engagement, linkedin_job_change, linkedin_competitor_followers, cross_pool, people_pool, github_issues, adzuna_jobs, corporate_registries, web_news_radar, web_pulse_hunter, google_places, web_profile, local_listings |
Request
curl -s -X PATCH "https://api.vendo-ai.com/v1/sdr/searches/$SEARCH_ID/hunters" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"enable": ["linkedin_competitor_followers", "web_profile"], "disable": ["hn_hiring"]}'Response
{
"data": {
"search_id": "9b1f0c3e-6a52-4d7e-9a43-2f6c1d8e7a10",
"enabled_sources": [
"linkedin_jobs",
"connected_people",
"linkedin_competitor_followers",
"web_profile"
],
"added": [
"linkedin_competitor_followers",
"web_profile"
],
"removed": [
"hn_hiring"
],
"note": "Added hunters start within ~10 minutes (budget-throttled ones within ~30); removed hunters stop at their next cycle."
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | A parameter is missing or invalid (message names it) |
| 400 | forbidden_field | The body carries a field that cannot be set here |
| 404 | not_found | The search/contact/job does not exist or belongs to another workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: sdr_set_hunters
GET/v1/sdr/searches/:id/hunters/:sourceHunter detail page
#get-v1-sdr-searches-id-hunters-source
One hunter of a search, as on /vendo-sdr/search/:id/hunter/:source: catalogue entry, enabled, run state, signals in 30 days, signals and contacts per day, the contacts it found (including pending and phone-only ones) and its settings. Read-only.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | SDR search (process) id |
source | path | string | yes | Hunter source id (GET /v1/sdr/hunters) one of: linkedin_jobs, linkedin_company, linkedin_activity, ai_web_hunter, social_signal_hunter, crunchbase_rss, hn_hiring, g2_reviews, tech_stack, connected_people, email_enricher, linkedin_engagement, linkedin_job_change, linkedin_competitor_followers, cross_pool, people_pool, github_issues, adzuna_jobs, corporate_registries, web_news_radar, web_pulse_hunter, google_places, web_profile, local_listings |
days | query | integer | Chart length 2-60, default 14 | |
contacts_limit | query | integer | 1-200, default 100 |
Request
curl -s "https://api.vendo-ai.com/v1/sdr/searches/$SEARCH_ID/hunters/$SOURCE" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Show the full response (55 lines)
{
"data": {
"search_id": "9b1f0c3e-6a52-4d7e-9a43-2f6c1d8e7a10",
"hunter": {
"id": "google_places",
"name": "Local businesses (Google Places)"
},
"enabled": true,
"state": {
"status": "running"
},
"signals_30d": 64,
"daily": {
"days": [
"2026-10-25",
"2026-10-26"
],
"signals": [
5,
3
],
"contacts": [
2,
1
]
},
"contacts": {
"items": [
{
"id": "1f8e2d3c-4b5a-4697-8a1b-2c3d4e5f6a7b",
"full_name": "Amir Patel",
"title": "Owner",
"company_name": "Smile Studio Manchester",
"email": "amir@smilestudio.co.uk",
"icp_group": "fits"
}
],
"total": 41
},
"settings": {
"industry_keywords": [
"dental clinic"
],
"target_regions": [
"GB"
],
"target_cities": [
{
"country": "GB",
"name": "Manchester"
}
]
}
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | A parameter is missing or invalid (message names it) |
| 404 | not_found | The search/contact/job does not exist or belongs to another workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: sdr_hunter_detail
PATCH/v1/sdr/searches/:id/hunters/:sourceConfigure one hunter
#patch-v1-sdr-searches-id-hunters-source
Turns one hunter on/off and/or sets the search fields it reads, limited to its editable inputs: competitor_pages, target_cities, decision_roles, linkedin_search_queries, industry_keywords, exclude_keywords, relevant_topics. These settings are search-wide, so every hunter reading the same field sees the change on its next cycle. decision_roles also rebuilds the ICP sentence, as in the wizard.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | SDR search (process) id |
source | path | string | yes | Hunter source id (GET /v1/sdr/hunters) one of: linkedin_jobs, linkedin_company, linkedin_activity, ai_web_hunter, social_signal_hunter, crunchbase_rss, hn_hiring, g2_reviews, tech_stack, connected_people, email_enricher, linkedin_engagement, linkedin_job_change, linkedin_competitor_followers, cross_pool, people_pool, github_issues, adzuna_jobs, corporate_registries, web_news_radar, web_pulse_hunter, google_places, web_profile, local_listings |
enabled | body | boolean | Switch this hunter on/off | |
settings | body | object | Subset of the hunter's inputs (see GET /v1/sdr/hunters) |
Request
curl -s -X PATCH "https://api.vendo-ai.com/v1/sdr/searches/$SEARCH_ID/hunters/$SOURCE" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"settings": {
"competitor_pages": [
"https://www.linkedin.com/company/dentally/",
"https://www.linkedin.com/company/software-of-excellence/"
]
},
"enabled": true
}'Response
{
"data": {
"search_id": "9b1f0c3e-6a52-4d7e-9a43-2f6c1d8e7a10",
"source": "linkedin_competitor_followers",
"enabled": true,
"settings": {
"competitor_pages": [
"https://www.linkedin.com/company/dentally/",
"https://www.linkedin.com/company/software-of-excellence/"
],
"target_regions": [
"GB"
]
},
"note": "Settings are search-wide: every hunter that reads the same field sees the new value on its next cycle."
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | A parameter is missing or invalid (message names it) |
| 400 | forbidden_field | The body carries a field that cannot be set here |
| 404 | not_found | The search/contact/job does not exist or belongs to another workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: sdr_configure_hunter
GET/v1/sdr/searches/:id/hunters/:source/dailySignals & contacts per day for one hunter
#get-v1-sdr-searches-id-hunters-source-daily
The hunter page chart (7/14/28 in the UI): signals and contacts per day for one hunter. Read-only.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | SDR search (process) id |
source | path | string | yes | Hunter source id (GET /v1/sdr/hunters) one of: linkedin_jobs, linkedin_company, linkedin_activity, ai_web_hunter, social_signal_hunter, crunchbase_rss, hn_hiring, g2_reviews, tech_stack, connected_people, email_enricher, linkedin_engagement, linkedin_job_change, linkedin_competitor_followers, cross_pool, people_pool, github_issues, adzuna_jobs, corporate_registries, web_news_radar, web_pulse_hunter, google_places, web_profile, local_listings |
days | query | integer | 2-60, default 14 |
Request
curl -s "https://api.vendo-ai.com/v1/sdr/searches/$SEARCH_ID/hunters/$SOURCE/daily" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"search_id": "9b1f0c3e-6a52-4d7e-9a43-2f6c1d8e7a10",
"source": "linkedin_jobs",
"days": [
"2026-10-24",
"2026-10-25",
"2026-10-26"
],
"signals": [
7,
4,
6
],
"contacts": [
3,
2,
2
]
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | A parameter is missing or invalid (message names it) |
| 404 | not_found | The search/contact/job does not exist or belongs to another workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: sdr_hunter_daily
POST/v1/sdr/searches/:id/run-nowRun hunters now
#post-v1-sdr-searches-id-run-now
The station "Run" button. With source: that hunter (validated; cross_pool and people_pool are automatic and rejected). Without source: every enabled runnable hunter, as documented (the backend alone defaulted to linkedin_jobs). A running hunter is woken instead of started twice. dry_run lists the hunters with paid-engine / LinkedIn-budget flags.
Credits: No per-run charge; contacts found cost 5 credits each · Protected: send {"dry_run": true} to preview, then {"confirm": true} to execute (protocol).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | SDR search (process) id |
source | body | string | One runnable hunter one of: linkedin_jobs, linkedin_company, linkedin_activity, ai_web_hunter, social_signal_hunter, crunchbase_rss, hn_hiring, g2_reviews, tech_stack, connected_people, email_enricher, linkedin_engagement, linkedin_job_change, linkedin_competitor_followers, github_issues, adzuna_jobs, corporate_registries, web_news_radar, web_pulse_hunter, google_places, web_profile, local_listings | |
dry_run | body | boolean | Preview | |
confirm | body | boolean | Execute |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/sdr/searches/$SEARCH_ID/run-now" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"source": "ai_web_hunter", "confirm": true}'Response
{
"data": {
"ok": true,
"message": "AI Web Hunter perpetual loop started",
"source": "ai_web_hunter"
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | A parameter is missing or invalid (message names it) |
| 400 | confirmation_required | Neither dry_run nor confirm was sent |
| 400 | forbidden_field | The body carries a field that cannot be set here |
| 404 | not_found | The search/contact/job does not exist or belongs to another workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
GET/v1/sdr/searches/:id/run-statePrimary run state for the search
#get-v1-sdr-searches-id-run-state
Read-only. Overall run state of a search (running / paused / idle) with the current action.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string | yes |
Request
curl -s "https://api.vendo-ai.com/v1/sdr/searches/$SEARCH_ID/run-state" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: sdr_run_state
GET/v1/sdr/searches/:id/run-statesPer-source map of hunter states
#get-v1-sdr-searches-id-run-states
Per-source map of run state (running / paused / idle) for a Signal Search.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string | yes |
Request
curl -s "https://api.vendo-ai.com/v1/sdr/searches/$SEARCH_ID/run-states" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: sdr_run_states
POST/v1/sdr/searches/:id/sources/pausePause one hunter
#post-v1-sdr-searches-id-sources-pause
Pauses one hunter; the others keep running (source required and validated; the search is ownership-checked). Response: ok, source, status, "pausing" when the hunter was running and stops at its next checkpoint, "paused" when it was idle (the pause is stored either way), and was (the run status before the call). Fixed (2026-10-26): the raw backend body {accepted, was} used to be passed through.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | SDR search (process) id |
source | body | string | yes | Hunter source id one of: linkedin_jobs, linkedin_company, linkedin_activity, ai_web_hunter, social_signal_hunter, crunchbase_rss, hn_hiring, g2_reviews, tech_stack, connected_people, email_enricher, linkedin_engagement, linkedin_job_change, linkedin_competitor_followers, cross_pool, people_pool, github_issues, adzuna_jobs, corporate_registries, web_news_radar, web_pulse_hunter, google_places, web_profile, local_listings |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/sdr/searches/$SEARCH_ID/sources/pause" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"source": "linkedin_activity"}'Response
{
"data": {
"ok": true,
"source": "linkedin_activity",
"status": "pausing",
"was": "running"
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | A parameter is missing or invalid (message names it) |
| 400 | forbidden_field | The body carries a field that cannot be set here |
| 404 | not_found | The search/contact/job does not exist or belongs to another workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: sdr_pause_hunter
POST/v1/sdr/searches/:id/sources/resumeResume one hunter
#post-v1-sdr-searches-id-sources-resume
Resumes one paused hunter (source required and validated). Response: ok, source, status (the hunter's run status after the call, "idle" until its next run) and state (the run-state map of the search, keyed by source). Fixed (2026-10-26): same documented shape as pause.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | SDR search (process) id |
source | body | string | yes | Hunter source id one of: linkedin_jobs, linkedin_company, linkedin_activity, ai_web_hunter, social_signal_hunter, crunchbase_rss, hn_hiring, g2_reviews, tech_stack, connected_people, email_enricher, linkedin_engagement, linkedin_job_change, linkedin_competitor_followers, cross_pool, people_pool, github_issues, adzuna_jobs, corporate_registries, web_news_radar, web_pulse_hunter, google_places, web_profile, local_listings |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/sdr/searches/$SEARCH_ID/sources/resume" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"source": "linkedin_activity"}'Response
{
"data": {
"ok": true,
"source": "linkedin_activity",
"status": "idle",
"state": {
"linkedin_activity": {
"search_id": "379007a6-306d-4816-89e5-df48af9958a7",
"source": "linkedin_activity",
"status": "idle",
"phase": null,
"current_action": null
}
}
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | A parameter is missing or invalid (message names it) |
| 400 | forbidden_field | The body carries a field that cannot be set here |
| 404 | not_found | The search/contact/job does not exist or belongs to another workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: sdr_resume_hunter
People (contacts) & ICP QA 14
GET/v1/sdr/peopleList contacts of a search
Contacts newest first. New filters: group (Fit/Uncertain/Not a fit/unchecked tabs) and source (contacts of one hunter, including pending and phone-only ones, as on the hunter page). Paging up to 200.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
search_id | query | uuid | yes | Search |
group | query | string | ICP group one of: fits, uncertain, unfit, unchecked | |
source | query | string | Hunter source one of: linkedin_jobs, linkedin_company, linkedin_activity, ai_web_hunter, social_signal_hunter, crunchbase_rss, hn_hiring, g2_reviews, tech_stack, connected_people, email_enricher, linkedin_engagement, linkedin_job_change, linkedin_competitor_followers, cross_pool, people_pool, github_issues, adzuna_jobs, corporate_registries, web_news_radar, web_pulse_hunter, google_places, web_profile, local_listings | |
company | query | string | Company name contains | |
role | query | string | Role category | |
min_relevance | query | integer | 0-100 | |
limit | query | integer | 1-200, default 50 | |
offset | query | integer | Paging offset |
Request
curl -s "https://api.vendo-ai.com/v1/sdr/people?search_id=$SEARCH_ID" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"items": [
{
"id": "1f8e2d3c-4b5a-4697-8a1b-2c3d4e5f6a7b",
"search_id": "9b1f0c3e-6a52-4d7e-9a43-2f6c1d8e7a10",
"full_name": "Sarah Collins",
"title": "Practice Manager",
"company_name": "Northgate Dental",
"email": "sarah@northgatedental.co.uk",
"linkedin_url": "https://www.linkedin.com/in/sarah-collins-dental",
"icp_group": "fits",
"enrichment_status": "valid",
"hot_score": 81,
"hot_level": 2,
"detected_at": "2026-10-25T14:20:00Z"
}
],
"total": 260,
"limit": 50,
"offset": 0
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | A parameter is missing or invalid (message names it) |
| 404 | not_found | The search/contact/job does not exist or belongs to another workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: sdr_list_personas
POST/v1/sdr/people/backfill-avatarsBackfill LinkedIn avatars of a search's contacts
#post-v1-sdr-people-backfill-avatars
Starts an asynchronous backfill of LinkedIn profile photos onto the contacts of one search (search_id in the body; the search must belong to your workspace).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
search_id | body | string (uuid) | yes | Search of this workspace |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/sdr/people/backfill-avatars" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"search_id": "<search_id>"}'Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
POST/v1/sdr/people/bulk-deleteRemove several contacts
#post-v1-sdr-people-bulk-delete
Multi-select delete of up to 500 contacts of one search. Every id is verified to belong to the search first (404 lists the ones that do not). dry_run counts them by group. No restore.
Protected: send {"dry_run": true} to preview, then {"confirm": true} to execute (protocol).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
search_id | body | uuid | yes | Search |
ids | body | uuid[] | yes | 1-500 contact ids |
dry_run | body | boolean | Preview | |
confirm | body | boolean | Execute |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/sdr/people/bulk-delete" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"search_id": "9b1f0c3e-6a52-4d7e-9a43-2f6c1d8e7a10",
"ids": [
"1f8e2d3c-4b5a-4697-8a1b-2c3d4e5f6a7b"
],
"dry_run": true
}'Response
{
"data": {
"dry_run": true,
"would_do": "Remove 1 contact(s) from this search (no restore).",
"estimated": {
"contacts": 1,
"by_group": {
"unfit": 1
}
},
"reversible": false
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | A parameter is missing or invalid (message names it) |
| 400 | confirmation_required | Neither dry_run nor confirm was sent |
| 400 | forbidden_field | The body carries a field that cannot be set here |
| 404 | not_found | The search/contact/job does not exist or belongs to another workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
POST/v1/sdr/people/export-to-audienceExport contacts to a custom audience
#post-v1-sdr-people-export-to-audience
Creates a custom audience from the search's contacts with an email. Fixed: always failed with 400 because the backend needs the user; the user and workspace are now taken from the API key. Adds fit_segment (default fit) and makes name required. No credits.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
search_id | body | uuid | yes | Search |
name | body | string | yes | New audience name |
fit_segment | body | string | Default fit one of: fit, fit_uncertain, all, uncertain, unfit, unchecked | |
only_with_email | body | boolean | Default true | |
roles | body | string[] | Role categories | |
ids | body | uuid[] | Explicit contacts (overrides roles) |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/sdr/people/export-to-audience" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"search_id": "9b1f0c3e-6a52-4d7e-9a43-2f6c1d8e7a10",
"name": "Dental owners — Oct",
"fit_segment": "fit_uncertain"
}'Response
{
"data": {
"ok": true,
"audience_id": "8e9f0a1b-2c3d-4e5f-8a9b-0c1d2e3f4a5b",
"audience_name": "Dental owners — Oct",
"contacts_exported": 172,
"with_email": 172,
"with_linkedin": 140
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | A parameter is missing or invalid (message names it) |
| 400 | forbidden_field | The body carries a field that cannot be set here |
| 404 | not_found | The search/contact/job does not exist or belongs to another workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
GET/v1/sdr/people/export.csvExport contacts of a search as a CSV file (not JSON). Requires search_id; optional company, role, min_relevance. Returns text/csv with a Content-Disposition attachment header. Contacts only, deliberately carries no scores or signal reasoning.
parity with the "Download contacts (CSV)" button. Returns the raw CSV file, NOT the { data } envelope every other route uses. That's deliberate: the whole point is a file you can pipe straight into a spreadsheet or another tool. Declared BEFORE people/:id/detail so Nest doesn't match "export.csv" as an :id. Contacts only, no scores, no signal reasoning. This file leaves our system, so it carries the people data the customer paid for and nothing about how we found them (same rule as the UI export). Export the contacts of an SDR search as CSV text (name, title, email, LinkedIn, location, company). Contacts ONLY, deliberately carries no scores or signal reasoning, because this file leaves the platform. Returns the raw CSV; can be large. Read-only, free.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
search_id | query | string | yes | |
company | query | string | ||
role | query | string | ||
min_relevance | query | number |
Request
curl -s "https://api.vendo-ai.com/v1/sdr/people/export.csv?search_id=$SEARCH_ID" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Returns { "data": … } with the resource(s) described above (raw CSV file, no envelope).
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
GET/v1/sdr/people/group-countsFit / Uncertain / Not a fit counts
#get-v1-sdr-people-group-counts
Counts per ICP group for the tabs and the "Who to contact" choice. only_with_email=true (default) counts email-ready contacts; false counts email-or-LinkedIn reachable ones, as outreach uses. Read-only.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
search_id | query | uuid | yes | Search |
only_with_email | query | boolean | Default true |
Request
curl -s "https://api.vendo-ai.com/v1/sdr/people/group-counts?search_id=$SEARCH_ID" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"fits": 150,
"uncertain": 42,
"unfit": 31,
"unchecked": 37,
"total": 260
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | A parameter is missing or invalid (message names it) |
| 404 | not_found | The search/contact/job does not exist or belongs to another workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
GET/v1/sdr/people/statsContact stats
Stats strip and chart of the People page for one search. days (7, 14 or 28, default 7) sets the window. Contact counters count email-ready (or phone) contacts that passed quality checks. total = contacts_total (all time); contacts_window_total = new contacts in the window (last_7d_total is the same number under its historical name, it follows days, not always 7 days); daily_series = contacts_daily_series (per day in the window; missing days = 0). signals_* count raw signals; quality_blocked_total, credits_spent, enrichment_breakdown, enriched_total, by_role, top_companies (top 20) with companies_total, and an outreach summary with outreach_daily per channel. Docs fixed (2026-10-26) to the real shape: there is no with_email or daily key.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
search_id | query | uuid | yes | Search |
days | query | integer | 7, 14 or 28 (default 7) one of: 7, 14, 28 |
Request
curl -s "https://api.vendo-ai.com/v1/sdr/people/stats?search_id=$SEARCH_ID" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Show the full response (84 lines)
{
"data": {
"total": 1665,
"last_7d_total": 862,
"daily_series": [
{
"date": "2026-10-25",
"count": 131
},
{
"date": "2026-10-26",
"count": 118
}
],
"contacts_total": 1665,
"contacts_window_total": 862,
"contacts_daily_series": [
{
"date": "2026-10-25",
"count": 131
},
{
"date": "2026-10-26",
"count": 118
}
],
"signals_total": 2509,
"signals_window_total": 1103,
"signals_daily_series": [
{
"date": "2026-10-26",
"count": 164
}
],
"quality_blocked_total": 37,
"credits_spent": 4120,
"enrichment_breakdown": {
"valid": 1665
},
"enriched_total": 1665,
"by_role": [
{
"role": "owner",
"count": 412
},
{
"role": "manager",
"count": 690
},
{
"role": "other",
"count": 563
}
],
"companies_total": 1212,
"top_companies": [
{
"company": "Northgate Dental",
"count": 4
}
],
"days": 7,
"outreach": {
"status": "active",
"emails_sent": 912,
"linkedin_sent": 640,
"replies": 37
},
"outreach_daily": {
"email": [
{
"date": "2026-10-26",
"count": 80
}
],
"linkedin": [
{
"date": "2026-10-26",
"count": 40
}
]
}
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | A parameter is missing or invalid (message names it) |
| 404 | not_found | The search/contact/job does not exist or belongs to another workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: sdr_persona_stats
DELETE/v1/sdr/people/:idRemove a contact
Soft-deletes one contact: hidden from every list, count, export and outreach queue. There is no restore route and no refund.
Protected: send {"dry_run": true} to preview, then {"confirm": true} to execute (protocol).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | Contact id |
search_id | body | uuid | yes | Search the contact belongs to (also accepted as query) |
dry_run | body | boolean | Preview | |
confirm | body | boolean | Execute |
Request
curl -s -X DELETE "https://api.vendo-ai.com/v1/sdr/people/$PERSON_ID" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"search_id": "9b1f0c3e-6a52-4d7e-9a43-2f6c1d8e7a10", "confirm": true}'Response
{
"data": {
"ok": true
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | A parameter is missing or invalid (message names it) |
| 400 | confirmation_required | Neither dry_run nor confirm was sent |
| 404 | not_found | The search/contact/job does not exist or belongs to another workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
GET/v1/sdr/people/:id/detailWhy was this person picked?
Provenance of one contact: hunter, query, triggering signal, scores and the reasoning chain. search_id is now required: membership is proven through the search's contact list because the service has no contact-to-workspace lookup (the previous ownership check always answered 404).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | Contact id |
search_id | query | uuid | yes | Search the contact belongs to |
Request
curl -s "https://api.vendo-ai.com/v1/sdr/people/$PERSON_ID/detail?search_id=$SEARCH_ID" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Show the full response (39 lines)
{
"data": {
"person": {
"id": "1f8e2d3c-4b5a-4697-8a1b-2c3d4e5f6a7b",
"full_name": "Sarah Collins",
"title": "Practice Manager",
"email": "sarah@northgatedental.co.uk",
"linkedin_url": "https://www.linkedin.com/in/sarah-collins-dental",
"location": "Leeds, United Kingdom",
"enrichment_status": "valid"
},
"company": {
"name": "Northgate Dental",
"domain": "northgatedental.co.uk",
"description": null
},
"scores": {
"relevance": 82,
"icp_fit": 0.86,
"intent": 0.7,
"total": 81
},
"signal": {
"type": "hiring_role",
"topic": "Practice Manager",
"source": "linkedin_jobs",
"source_label": "LinkedIn Jobs hunter (hiring signals)",
"detected_at": "2026-10-24T08:00:00Z"
},
"why": [
{
"step": "Signal",
"detail": "Hiring a Practice Manager"
}
],
"search_id": "9b1f0c3e-6a52-4d7e-9a43-2f6c1d8e7a10",
"icp_group": "fits"
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | A parameter is missing or invalid (message names it) |
| 404 | not_found | The search/contact/job does not exist or belongs to another workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
PATCH/v1/sdr/people/:id/groupMove a contact between ICP groups
Overrides the ICP verdict (fits, uncertain, unfit). Moving to fits a contact the ICP pre-check left free charges 5 credits; that move requires confirm (dry_run shows the cost), and 402 insufficient_credits leaves the group unchanged. Other moves are free and run without confirm. To remove a contact use DELETE.
Credits: 5 credits when a free (pre-check skipped) contact is moved to fits; otherwise 0 · Protected: send {"dry_run": true} to preview, then {"confirm": true} to execute (protocol).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | Contact id |
search_id | body | uuid | yes | Search the contact belongs to |
group | body | string | yes | Target group one of: fits, uncertain, unfit |
dry_run | body | boolean | Preview | |
confirm | body | boolean | Required when the move charges credits |
Request
curl -s -X PATCH "https://api.vendo-ai.com/v1/sdr/people/$PERSON_ID/group" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"search_id": "9b1f0c3e-6a52-4d7e-9a43-2f6c1d8e7a10", "group": "fits", "confirm": true}'Response
{
"data": {
"ok": true,
"group": "fits",
"credits_charged": 5
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | A parameter is missing or invalid (message names it) |
| 400 | forbidden_field | The body carries a field that cannot be set here |
| 404 | not_found | The search/contact/job does not exist or belongs to another workspace |
| 400 | confirmation_required | The move charges 5 credits and confirm was not sent |
| 402 | insufficient_credits | Not enough credits to accept the contact |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
PATCH/v1/sdr/searches/:id/icp-qa/autocorrectKeyword auto-correction on/off
#patch-v1-sdr-searches-id-icp-qa-autocorrect
Allows the ICP check to rewrite the search's industry keywords automatically (default off). Roll back any change with icp-qa/rollback.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | SDR search (process) id |
enabled | body | boolean | yes | On/off |
Request
curl -s -X PATCH "https://api.vendo-ai.com/v1/sdr/searches/$SEARCH_ID/icp-qa/autocorrect" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"enabled": true}'Response
{
"data": {
"ok": true,
"enabled": true
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | A parameter is missing or invalid (message names it) |
| 400 | forbidden_field | The body carries a field that cannot be set here |
| 404 | not_found | The search/contact/job does not exist or belongs to another workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
GET/v1/sdr/searches/:id/icp-qa/historyICP check history
#get-v1-sdr-searches-id-icp-qa-history
Past QA runs for the quality chart and Search-history tab, newest first, plus whether keyword auto-correction is on. Read-only.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | SDR search (process) id |
limit | query | integer | 1-100, default 30 |
Request
curl -s "https://api.vendo-ai.com/v1/sdr/searches/$SEARCH_ID/icp-qa/history" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"runs": [
{
"id": "7c1d2e3f-4a5b-4c6d-8e7f-9a0b1c2d3e4f",
"checked_count": 40,
"fits_count": 25,
"uncertain_count": 9,
"unfit_count": 6,
"quality_pct": 63,
"created_at": "2026-10-26T11:10:00Z"
}
],
"autocorrect_enabled": false
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | A parameter is missing or invalid (message names it) |
| 404 | not_found | The search/contact/job does not exist or belongs to another workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: sdr_icp_qa_history
POST/v1/sdr/searches/:id/icp-qa/rollbackRoll keywords back to a QA run
#post-v1-sdr-searches-id-icp-qa-rollback
Restores the search's industry_keywords to the snapshot taken before the given QA run (undo of an auto-correction). The run must belong to this search.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | SDR search (process) id |
run_id | body | uuid | yes | QA run id from history |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/sdr/searches/$SEARCH_ID/icp-qa/rollback" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"run_id": "7c1d2e3f-4a5b-4c6d-8e7f-9a0b1c2d3e4f"}'Response
{
"data": {
"search_id": "9b1f0c3e-6a52-4d7e-9a43-2f6c1d8e7a10",
"run_id": "7c1d2e3f-4a5b-4c6d-8e7f-9a0b1c2d3e4f",
"industry_keywords": [
"dental clinic"
]
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | A parameter is missing or invalid (message names it) |
| 400 | forbidden_field | The body carries a field that cannot be set here |
| 404 | not_found | The search/contact/job does not exist or belongs to another workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: sdr_icp_qa_rollback
POST/v1/sdr/searches/:id/icp-qa/runICP check now
#post-v1-sdr-searches-id-icp-qa-run
People page "Check now": classifies the newest unchecked contacts as Fit / Uncertain / Not a fit with AI (not billed) and records a QA run (quality %, keyword suggestions). run is null when nothing is unchecked or the search has no ICP summary. Up to ~2 minutes.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | SDR search (process) id |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/sdr/searches/$SEARCH_ID/icp-qa/run" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"run": {
"id": "7c1d2e3f-4a5b-4c6d-8e7f-9a0b1c2d3e4f",
"search_id": "9b1f0c3e-6a52-4d7e-9a43-2f6c1d8e7a10",
"checked_count": 40,
"fits_count": 25,
"uncertain_count": 9,
"unfit_count": 6,
"quality_pct": 63,
"keywords_before": [
"dental clinic"
],
"keywords_after": [
"dental clinic",
"dental practice"
],
"created_at": "2026-10-26T11:10:00Z"
}
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | The search/contact/job does not exist or belongs to another workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: sdr_icp_qa_run
Outreach 11
GET/v1/sdr/outreach/sales-processCompleted SDR outreach campaigns for Sales Process
#get-v1-sdr-outreach-sales-process
Read-only. SDR outreach processes of the workspace as listed on the Sales Process page (status, channels, counters).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
agent_id | query | string |
Request
curl -s "https://api.vendo-ai.com/v1/sdr/outreach/sales-process" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
GET/v1/sdr/searches/:id/outreachGet outreach status for one Signal Search
#get-v1-sdr-searches-id-outreach
Read the SDR outreach campaign, channels enabled, ready/queued/sent counts, current content config.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string | yes |
Request
curl -s "https://api.vendo-ai.com/v1/sdr/searches/$SEARCH_ID/outreach" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: sdr_get_outreach
DELETE/v1/sdr/searches/:id/outreachDelete outreach campaign (paired dry_run/confirm)
#delete-v1-sdr-searches-id-outreach
Delete the SDR outreach campaign. The Signal Search and its hunters CONTINUE running. Only call after sdr_preview_delete_outreach.
Protected: send {"dry_run": true} to preview, then {"confirm": true} to execute (protocol).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string | yes | |
dry_run | body | boolean | true = preview only (nothing happens) | |
confirm | body | boolean | true = execute. Send exactly one of dry_run / confirm |
Request
curl -s -X DELETE "https://api.vendo-ai.com/v1/sdr/searches/$SEARCH_ID/outreach" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"dry_run": true}'Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
GET/v1/sdr/searches/:id/outreach/contentRead outreach templates
#get-v1-sdr-searches-id-outreach-content
Read the configured email subject/body templates and LinkedIn invite template for SDR outreach.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string | yes |
Request
curl -s "https://api.vendo-ai.com/v1/sdr/searches/$SEARCH_ID/outreach/content" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
PATCH/v1/sdr/searches/:id/outreach/contentEdit outreach messages
#patch-v1-sdr-searches-id-outreach-content
Outreach windows 2-3, now with an explicit allow-list: email {mode ai|fixed|html, prompt, tone, variants ≤5 {subject, body}, followups ≤7 {delay_days, prompt|subject+body}, html_steps, signature, auto-reply…} and linkedin {invite on/off, invite_mode/followup_mode ai|exact with texts, followup_delay_minutes, conversation_rules ≤3000, auto-reply delays, signature_mode, calendly_url…}. Placeholders: {{first_name}} {{last_name}} {{full_name}} {{company_name}} {{position}} {{city}} {{country}}; a LinkedIn message with an unresolved {{…}} is not sent. No model call on save.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | SDR search (process) id |
email | body | object | Email settings (partial) | |
linkedin | body | object | LinkedIn settings (partial) |
Request
curl -s -X PATCH "https://api.vendo-ai.com/v1/sdr/searches/$SEARCH_ID/outreach/content" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"email": {
"mode": "fixed",
"variants": [
{
"subject": "{{company_name}} recalls",
"body": "Hi {{first_name}}, …"
}
],
"followups": [
{
"delay_days": 3,
"subject": "Re: recalls",
"body": "Quick follow-up, {{first_name}}"
}
]
},
"linkedin": {
"invite_enabled": true,
"invite_mode": "ai",
"conversation_rules": "Goal: a 15-minute demo. Offer https://cal.com/brightsmile/demo."
}
}'Response
Show the full response (35 lines)
{
"data": {
"search_id": "9b1f0c3e-6a52-4d7e-9a43-2f6c1d8e7a10",
"campaign_id": "c1d2e3f4-a5b6-4c7d-8e9f-0a1b2c3d4e5f",
"channels": [
"email",
"linkedin"
],
"config": {
"email": {
"mode": "fixed",
"variants": [
{
"subject": "{{company_name}} recalls",
"body": "Hi {{first_name}}, …"
}
],
"max_followups": 1,
"followup_interval_days": 3
},
"linkedin": {
"invite_enabled": true,
"invite_mode": "ai",
"followup_delay_minutes": 0,
"reply_delay_min_minutes": 2,
"reply_delay_max_minutes": 7
}
},
"default_email_signature": "Anna Smith\nBrightsmile Software",
"preview": {
"samples": [],
"cached": false
}
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | A parameter is missing or invalid (message names it) |
| 400 | forbidden_field | The body carries a field that cannot be set here |
| 404 | not_found | The search/contact/job does not exist or belongs to another workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
POST/v1/sdr/searches/:id/outreach/content/previewRender a personalised message preview
#post-v1-sdr-searches-id-outreach-content-preview
Render an example personalized email/LinkedIn message for a specific contact. Costs ~1 Claude call.
Protected: send {"dry_run": true} to preview, then {"confirm": true} to execute (protocol).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string | yes | |
dry_run | body | boolean | true = preview only (nothing happens) | |
confirm | body | boolean | true = execute. Send exactly one of dry_run / confirm |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/sdr/searches/$SEARCH_ID/outreach/content/preview" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"dry_run": true}'Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
POST/v1/sdr/searches/:id/outreach/launchLaunch SDR outreach
#post-v1-sdr-searches-id-outreach-launch
"Save and Launch" with the full contract: every window-1 field (incl. icp_segment, LinkedIn accounts, hours, time zone, CRM branch) is validated; omitted fields come from the saved settings, then the defaults, like the UI. sender_mode is rejected (use email_sender_mode). dry_run now reports the effective settings, contacts in the segment, ready email/LinkedIn contacts, predicted blockers, the paid-plan rule and the delivery window. Real messages start after confirm.
Credits: 5 credits per newly queued contact not charged before · Protected: send {"dry_run": true} to preview, then {"confirm": true} to execute (protocol).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | SDR search (process) id |
channels | body | string[] | Subset of email, linkedin. Default: saved value, else both one of: email, linkedin | |
email_sender_mode | body | string | managed = Vendo sender pool (paid plans only, else 403); client_owned = the workspace's own mailboxes (connect them via /v1/email/mailboxes). Default: saved value, else client_owned when a mailbox exists, else managed one of: managed, client_owned | |
client_email_provider | body | string|null | google or microsoft (client_owned only) one of: google, microsoft | |
selected_linkedin_account_ids | body | uuid[] | LinkedIn accounts to send from (ids from GET …/outreach linkedin_accounts). Empty = every connected account in status OK | |
timezone | body | string | IANA time zone of the working window, default UTC | |
work_start_hour | body | integer | 0-23, default 9 | |
work_end_hour | body | integer | 0-23, default 18 | |
exclude_weekends | body | boolean | Default true | |
daily_email_limit | body | integer | 1-500, default 50 | |
daily_linkedin_limit | body | integer | 1-200, default 40 | |
branch_id | body | uuid|null|'general' | CRM branch that receives replies; null or 'general' = General. Must be a branch you can see (GET /v1/crm/branches; create one with POST /v1/crm/branches) | |
icp_segment | body | string | Who to contact: fit (Fit + unchecked, default), fit_uncertain, all; legacy uncertain, unfit, unchecked one of: fit, fit_uncertain, all, uncertain, unfit, unchecked | |
dry_run | body | boolean | Preview | |
confirm | body | boolean | Execute |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/sdr/searches/$SEARCH_ID/outreach/launch" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"channels": [
"email",
"linkedin"
],
"email_sender_mode": "client_owned",
"client_email_provider": "google",
"selected_linkedin_account_ids": [
"b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e"
],
"timezone": "Europe/London",
"work_start_hour": 9,
"work_end_hour": 17,
"exclude_weekends": true,
"daily_email_limit": 50,
"daily_linkedin_limit": 30,
"branch_id": null,
"icp_segment": "fit",
"confirm": true
}'Response
Show the full response (49 lines)
{
"data": {
"search_id": "9b1f0c3e-6a52-4d7e-9a43-2f6c1d8e7a10",
"delivery_window": {
"sending_now": true,
"next_send_at": null,
"timezone": "Europe/London",
"work_start_hour": 9,
"work_end_hour": 17,
"exclude_weekends": true,
"queued": 48
},
"min_contacts_required": 1,
"ready_contacts": 190,
"linkedin_ready_contacts": 140,
"can_launch": true,
"campaign": {
"id": "c1d2e3f4-a5b6-4c7d-8e9f-0a1b2c3d4e5f",
"status": "active",
"channels": [
"email",
"linkedin"
],
"email_sender_mode": "client_owned",
"timezone": "Europe/London",
"work_start_hour": 9,
"work_end_hour": 17,
"exclude_weekends": true,
"branch_id": null,
"settings": {
"icp_segment": "fit",
"blocked_reasons": []
}
},
"linkedin_accounts": [
{
"id": "b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e",
"name": "Anna Smith",
"status": "OK",
"can_send": true
}
],
"client_mailboxes": {
"ready": 2,
"warming": 1,
"error": 0
}
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | A parameter is missing or invalid (message names it) |
| 400 | confirmation_required | Neither dry_run nor confirm was sent |
| 400 | forbidden_field | The body carries a field that cannot be set here |
| 404 | not_found | The search/contact/job does not exist or belongs to another workspace |
| 400 | invalid_request | Fewer ready contacts than the launch minimum, or no channel |
| 403 | forbidden | Managed senders on a workspace without a paid plan |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
POST/v1/sdr/searches/:id/outreach/linkedin/connectLink LinkedIn accounts to this outreach
#post-v1-sdr-searches-id-outreach-linkedin-connect
Returns a hosted link where the user connects a LinkedIn account for this SDR's outreach. A human must open it; then pick the account in selected_linkedin_account_ids.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string | yes |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/sdr/searches/$SEARCH_ID/outreach/linkedin/connect" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
POST/v1/sdr/searches/:id/outreach/pausePause outreach delivery
#post-v1-sdr-searches-id-outreach-pause
Stop sending new messages on this SDR campaign. Reversible.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string | yes |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/sdr/searches/$SEARCH_ID/outreach/pause" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: sdr_pause_outreach
POST/v1/sdr/searches/:id/outreach/resumeResume outreach delivery
#post-v1-sdr-searches-id-outreach-resume
Resume a paused SDR outreach campaign.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string | yes |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/sdr/searches/$SEARCH_ID/outreach/resume" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: sdr_resume_outreach
PATCH/v1/sdr/searches/:id/outreach/settingsSave outreach settings without launching
#patch-v1-sdr-searches-id-outreach-settings
Outreach window 1 "Save": Who to contact, channels, sender mode and provider, LinkedIn accounts, time zone, hours, weekends, daily limits and CRM branch. Partial: omitted fields keep their saved values (else defaults). Never launches: a new process stays draft; a running one keeps its status and applies the change at once, widening icp_segment on a running process queues more contacts (5 credits each). Own mailboxes are managed via /v1/email/mailboxes.
Credits: 0 for a draft; on a running process newly queued contacts cost 5 credits each
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | SDR search (process) id |
channels | body | string[] | Subset of email, linkedin. Default: saved value, else both one of: email, linkedin | |
email_sender_mode | body | string | managed = Vendo sender pool (paid plans only, else 403); client_owned = the workspace's own mailboxes (connect them via /v1/email/mailboxes). Default: saved value, else client_owned when a mailbox exists, else managed one of: managed, client_owned | |
client_email_provider | body | string|null | google or microsoft (client_owned only) one of: google, microsoft | |
selected_linkedin_account_ids | body | uuid[] | LinkedIn accounts to send from (ids from GET …/outreach linkedin_accounts). Empty = every connected account in status OK | |
timezone | body | string | IANA time zone of the working window, default UTC | |
work_start_hour | body | integer | 0-23, default 9 | |
work_end_hour | body | integer | 0-23, default 18 | |
exclude_weekends | body | boolean | Default true | |
daily_email_limit | body | integer | 1-500, default 50 | |
daily_linkedin_limit | body | integer | 1-200, default 40 | |
branch_id | body | uuid|null|'general' | CRM branch that receives replies; null or 'general' = General. Must be a branch you can see (GET /v1/crm/branches; create one with POST /v1/crm/branches) | |
icp_segment | body | string | Who to contact: fit (Fit + unchecked, default), fit_uncertain, all; legacy uncertain, unfit, unchecked one of: fit, fit_uncertain, all, uncertain, unfit, unchecked | |
dry_run | body | boolean | Preview the effective settings and readiness |
Request
curl -s -X PATCH "https://api.vendo-ai.com/v1/sdr/searches/$SEARCH_ID/outreach/settings" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"icp_segment": "fit_uncertain",
"timezone": "Europe/London",
"work_start_hour": 8,
"work_end_hour": 17
}'Response
Show the full response (41 lines)
{
"data": {
"search_id": "9b1f0c3e-6a52-4d7e-9a43-2f6c1d8e7a10",
"delivery_window": null,
"min_contacts_required": 1,
"ready_contacts": 190,
"linkedin_ready_contacts": 140,
"can_launch": true,
"campaign": {
"id": "c1d2e3f4-a5b6-4c7d-8e9f-0a1b2c3d4e5f",
"status": "draft",
"channels": [
"email",
"linkedin"
],
"email_sender_mode": "client_owned",
"timezone": "Europe/London",
"work_start_hour": 9,
"work_end_hour": 17,
"exclude_weekends": true,
"branch_id": null,
"settings": {
"icp_segment": "fit",
"blocked_reasons": []
}
},
"linkedin_accounts": [
{
"id": "b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e",
"name": "Anna Smith",
"status": "OK",
"can_send": true
}
],
"client_mailboxes": {
"ready": 2,
"warming": 1,
"error": 0
}
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | A parameter is missing or invalid (message names it) |
| 400 | forbidden_field | The body carries a field that cannot be set here |
| 404 | not_found | The search/contact/job does not exist or belongs to another workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
Search analytics 10
GET/v1/sdr/searches/:id/companies/export.csvExport companies (CSV)
#get-v1-sdr-searches-id-companies-export-csv
Raw CSV (not the {data} envelope) of every company the search found: Company, Domain, Heat, ICP fit, Signal count, Signal types, Contact status, Contacts found. UTF-8 with BOM for Excel.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | SDR search (process) id |
Request
curl -s "https://api.vendo-ai.com/v1/sdr/searches/$SEARCH_ID/companies/export.csv" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"csv": "Company,Domain,Heat,ICP fit,Signal count,Signal types,Contact status,Contacts found\r\nNorthgate Dental,northgatedental.co.uk,82,fit,3,hiring_role | news_mention,Contact found,2"
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | The search/contact/job does not exist or belongs to another workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
GET/v1/sdr/searches/:id/companies/:companyIdHot company popup
#get-v1-sdr-searches-id-companies-companyid
One company of a search: card from the companies database (website, logo, size, location, links), decision-makers found with fit, other known people at the company and its live signals. part=card returns only the card and people pool and may wait up to 20 s; the default full view answers in ~2.5-6.5 s with the card when available.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | SDR search (process) id |
companyId | path | uuid | yes | company_id from company-heat |
part | query | string | full (default) or card one of: full, card | |
name | query | string | Company name hint for domain resolution | |
domain | query | string | Domain hint |
Request
curl -s "https://api.vendo-ai.com/v1/sdr/searches/$SEARCH_ID/companies/$COMPANY_ID" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Show the full response (44 lines)
{
"data": {
"search_id": "9b1f0c3e-6a52-4d7e-9a43-2f6c1d8e7a10",
"company_id": "5a6b7c8d-9e0f-4a1b-8c2d-3e4f5a6b7c8d",
"part": "full",
"card_ready": true,
"resolved_domain": "northgatedental.co.uk",
"heat": 82,
"company": {
"id": "5a6b7c8d-9e0f-4a1b-8c2d-3e4f5a6b7c8d",
"name": "Northgate Dental",
"website": "https://northgatedental.co.uk",
"logo_url": null,
"description": "Family dental practice in Leeds.",
"industry": "Dental",
"country": "GB",
"city": "Leeds",
"employee_count": 18,
"linkedin_url": null,
"tags": []
},
"people": [
{
"full_name": "Sarah Collins",
"title": "Practice Manager",
"icp_group": "fits"
}
],
"pool_people": [
{
"full_name": "Tom Reed",
"title": "Dentist",
"linkedin_url": "https://www.linkedin.com/in/tomreed"
}
],
"signals": [
{
"signal_type": "hiring_role",
"topic": "Practice Manager",
"detected_at": "2026-10-24T08:00:00Z"
}
]
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | A parameter is missing or invalid (message names it) |
| 404 | not_found | The search/contact/job does not exist or belongs to another workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: sdr_company_detail
GET/v1/sdr/searches/:id/eventsLive hunter events (polling)
#get-v1-sdr-searches-id-events
Polling replacement for the search page's live stream: connector start/finish, queries, companies resolved, signals saved, contacts created/enriched. Pass since = next_since from the previous call to get only newer events. Best effort: the last 150 events per search are kept in memory and lost on a service restart.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | SDR search (process) id |
since | query | datetime | ISO timestamp; only newer events | |
limit | query | integer | 1-200, default 100 |
Request
curl -s "https://api.vendo-ai.com/v1/sdr/searches/$SEARCH_ID/events" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"search_id": "9b1f0c3e-6a52-4d7e-9a43-2f6c1d8e7a10",
"events": [
{
"id": "ev_9f2c",
"ts": "2026-10-26T11:02:03.120Z",
"kind": "signal_saved",
"severity": "success",
"source": "linkedin_jobs",
"message": "Northgate Dental is hiring a Practice Manager"
}
],
"next_since": "2026-10-26T11:02:03.120Z",
"buffer_note": "Best-effort live feed: the last 150 events per search are kept in memory and are lost on a service restart."
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | A parameter is missing or invalid (message names it) |
| 404 | not_found | The search/contact/job does not exist or belongs to another workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: sdr_events
GET/v1/sdr/searches/:id/icp-reviewICP Review panel
#get-v1-sdr-searches-id-icp-review
Uncertain contacts waiting for re-check, results of the last pass, totals and the next scheduled run (weekly). Read-only.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | SDR search (process) id |
Request
curl -s "https://api.vendo-ai.com/v1/sdr/searches/$SEARCH_ID/icp-review" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"pending": 34,
"lastRunAt": "2026-10-25T06:00:00Z",
"lastRunFits": 11,
"lastRunUnfit": 7,
"lastRunForced": false,
"totalReviewed": 120,
"totalFits": 46,
"nextRunAt": "2026-11-01T06:00:00Z"
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | The search/contact/job does not exist or belongs to another workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: sdr_icp_review
POST/v1/sdr/searches/:id/icp-review/runRe-check Uncertain contacts now
#post-v1-sdr-searches-id-icp-review-run
"Re-check now": re-judges this search's Uncertain contacts with the same bounded pass as the weekly job; a second run while one is in flight is refused by the service. Contacts moved to Fit become billable (5 credits) when used. May take up to ~4 minutes.
Credits: No charge for the check; contacts moved to Fit cost 5 credits when used
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | SDR search (process) id |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/sdr/searches/$SEARCH_ID/icp-review/run" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"ok": true,
"checked": 34,
"fits": 11,
"unfit": 7
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | The search/contact/job does not exist or belongs to another workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: sdr_icp_review_run
POST/v1/sdr/searches/:id/learning-recomputeManually trigger the learning loop
#post-v1-sdr-searches-id-learning-recompute
Recalculate per-signal-type weights from outreach outcomes (replies/meetings vs sends) for one search. Safe, no cost; use when the user asks to refresh/recalculate Signal Learning.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string | yes |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/sdr/searches/$SEARCH_ID/learning-recompute" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
GET/v1/sdr/searches/:id/outreach/activity-feedRecent discovery + outreach events for a search, newest first. Optional limit (default 40).
#get-v1-sdr-searches-id-outreach-activity-feed
Live activity ticker for one search, the merged discovery + outreach feed the dashboard polls (Phase 7). Polling, not SSE: streaming over an API key needs its own auth/backpressure design, and this answers the same question for integrators. Recent discovery + outreach events for one SDR search, newest first, the same ticker the dashboard shows. Read-only, free.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string | yes | |
limit | query | number | Default 40 |
Request
curl -s "https://api.vendo-ai.com/v1/sdr/searches/$SEARCH_ID/outreach/activity-feed" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: sdr_activity_feed
GET/v1/sdr/searches/:id/outreach/summarySent / queued counters per channel for a search.
#get-v1-sdr-searches-id-outreach-summary
Sent / queued counters per channel for one search. Per-channel sent and queued counts for one SDR search. Read-only, free.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string | yes |
Request
curl -s "https://api.vendo-ai.com/v1/sdr/searches/$SEARCH_ID/outreach/summary" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: sdr_outreach_summary
GET/v1/sdr/searches/:id/statsSignal stats by hunter
KPI cards and raw sources of a search. The window parameter is now days (1-90, default 30) and is actually applied (days_back was ignored by the backend; it is still accepted as an alias).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | SDR search (process) id |
days | query | integer | 1-90, default 30 |
Request
curl -s "https://api.vendo-ai.com/v1/sdr/searches/$SEARCH_ID/stats" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"search_id": "9b1f0c3e-6a52-4d7e-9a43-2f6c1d8e7a10",
"days_back": 30,
"fresh_signals_total": 702,
"signal_sources": [],
"pipeline_sources": [
{
"source": "connected_people",
"count": 260
},
{
"source": "email_enricher",
"count": 190
}
],
"by_source": [
{
"source": "linkedin_jobs",
"count": 182
}
],
"generated_at": "2026-10-26T11:00:00Z"
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | A parameter is missing or invalid (message names it) |
| 404 | not_found | The search/contact/job does not exist or belongs to another workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: sdr_signal_stats
GET/v1/sdr/searches/:id/stats/dailySignals per day per hunter
#get-v1-sdr-searches-id-stats-daily
Station sparklines: signals per day for every hunter of a search. Read-only.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | SDR search (process) id |
days | query | integer | 2-60, default 14 |
Request
curl -s "https://api.vendo-ai.com/v1/sdr/searches/$SEARCH_ID/stats/daily" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"search_id": "9b1f0c3e-6a52-4d7e-9a43-2f6c1d8e7a10",
"days": [
"2026-10-25",
"2026-10-26"
],
"series": {
"linkedin_jobs": [
7,
6
],
"crunchbase_rss": [
2,
0
]
}
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | A parameter is missing or invalid (message names it) |
| 404 | not_found | The search/contact/job does not exist or belongs to another workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: sdr_daily_by_hunter
Campaigns API 66
Classic multi-channel campaigns: lifecycle, wizard previews, emails and replies, AI conversation threads, LinkedIn, mailboxes and senders, phone campaigns, monitoring and content accounts.
Lifecycle 12
GET/v1/campaignsList campaigns
Lists classic Sales Process campaigns of the workspace with status and live stats. Archived campaigns are hidden unless include_archived=true, like the Archived checkbox in the app.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
include_archived | query | boolean | Also return archived campaigns. |
Request
curl -s "https://api.vendo-ai.com/v1/campaigns" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Show the full response (70 lines)
{
"data": [
{
"id": "c3a7e2f4-1b5d-4e8a-9c0f-6d2b8a4e7f15",
"name": "UK SaaS founders — Q4",
"status": "active",
"agent_id": "a91d4f60-2c3b-4e7a-8f15-0b6c9d2e4a71",
"branch_id": null,
"audience_id": "5f1c2a9e-8b7d-4c3e-9a61-2d4f7b0c1e83",
"settings": {
"channels": [
"email",
"linkedin"
],
"dailyLimit": 80,
"dailyEmailLimit": 80,
"dailyLinkedinLimit": 40,
"timezone": "Europe/London",
"workingHours": [
9,
18
],
"excludeWeekends": true,
"emailSenderMode": "client_owned",
"trackOpens": true,
"emailContent": {
"mode": "ai",
"prompt": "Short, specific, one question.",
"tone": "friendly",
"followUps": [
{
"delayDays": 3,
"prompt": "New angle: case study"
}
]
},
"linkedinContent": {
"mode": "ai",
"invite_tone": "friendly",
"followup_delay_minutes": 60
},
"selectedLinkedinAccountIds": [
"e4b8c2d1-7a3f-4b9e-8c1d-5f2a6e0b9c34"
],
"customInstructions": "Offer a 15-minute call. Use the formal form of address."
},
"sequence": [
{
"step": 1,
"delayDays": 0
},
{
"step": 2,
"delayDays": 3
}
],
"stats": {
"totalContacts": 412,
"emailsSent": 0,
"emailsOpened": 0,
"linksClicked": 0,
"repliesReceived": 0,
"meetingsBooked": 0
},
"created_at": "2026-10-26T09:12:44.000Z",
"updated_at": "2026-10-26T09:14:02.000Z"
}
],
"has_more": false
}Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: list_campaigns
POST/v1/campaignsCreate a campaign (draft)
Creates a campaign in DRAFT status; nobody is contacted until launch. Accepts every wizard setting; settings the backend create step does not store (trackOpens, trackLinks, stopOnNegative, speedTier) are applied right after create, as the app autosave does. agentId, branchId, selectedLinkedinAccountIds and senderEmailIds are checked against the workspace. customAudienceId builds the campaign audience from a READY uploaded/CRM list.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
name | body | string | yes | Campaign name (≤200). |
audienceId | body | uuid | Campaign audience id (POST /v1/audiences). Required unless customAudienceId is given. | |
customAudienceId | body | uuid | Id of a READY custom audience; a campaign audience is created from it. | |
agentId | body | uuid | Sales agent. Required before launch (AGENT_REQUIRED). | |
branchId | body | uuid|null | CRM branch for replies; null = General. | |
description | body | string | Free text; generated from the audience when omitted. | |
settings | body | object | Wizard settings (all optional, merged on update). channels [email|linkedin|phone_call]; dailyLimit (new contacts/day, ≤ sender capacity); dailyEmailLimit; dailyLinkedinLimit (campaign-wide invites/day across all accounts); timezone (IANA); workingHours [start 0–23, end 1–24]; excludeWeekends; campaignDurationDays; customInstructions (Communication Rules ≤6000: CTA, links, promises; also sets the ти/Ви, du/Sie form of address); emailSignature (HTML); notifyEmails[]; ccEmail; autoResponseEnabled; autoResponseDelayMinutes; followUpInterval & maxFollowUps (legacy, used when emailContent.followUps is empty); trackOpens; trackLinks; stopOnNegative; speedTier [conservative|balanced|aggressive]; emailSenderMode [managed|client_owned] (managed needs a paid plan to launch); clientEmailProvider [google|microsoft|null]; senderEmailIds[] (ids from GET /v1/email/senders; empty = auto-assign); emailContent {mode ai|fixed|html, prompt, tone formal|casual|friendly, variants ≤5 {subject, body}, htmlSteps ≤20 {subject, html, delayDays}, followUps ≤7 {delayDays 1–90, prompt | subject+body}}; linkedinContent {mode ai|fixed, invite_prompt, invite_tone, invite_exact_text ≤300, followup_delay_minutes 0–43200, followup_exact_text ≤1500}; selectedLinkedinAccountIds[] (ids from GET /v1/linkedin/accounts; empty = all connected at launch); linkedinAutoReplyDisabled. Other keys are ignored and listed in ignored_settings. |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/campaigns" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "UK SaaS founders — Q4",
"audienceId": "5f1c2a9e-8b7d-4c3e-9a61-2d4f7b0c1e83",
"agentId": "a91d4f60-2c3b-4e7a-8f15-0b6c9d2e4a71",
"settings": {
"channels": [
"email",
"linkedin"
],
"dailyLimit": 80,
"dailyEmailLimit": 80,
"dailyLinkedinLimit": 40,
"timezone": "Europe/London",
"workingHours": [
9,
18
],
"excludeWeekends": true,
"emailSenderMode": "client_owned",
"trackOpens": true,
"emailContent": {
"mode": "ai",
"prompt": "Short, specific, one question.",
"tone": "friendly",
"followUps": [
{
"delayDays": 3,
"prompt": "New angle: case study"
}
]
},
"linkedinContent": {
"mode": "ai",
"invite_tone": "friendly",
"followup_delay_minutes": 60
},
"selectedLinkedinAccountIds": [
"e4b8c2d1-7a3f-4b9e-8c1d-5f2a6e0b9c34"
],
"customInstructions": "Offer a 15-minute call. Use the formal form of address."
}
}'Response
Show the full response (67 lines)
{
"data": {
"id": "c3a7e2f4-1b5d-4e8a-9c0f-6d2b8a4e7f15",
"name": "UK SaaS founders — Q4",
"status": "draft",
"agent_id": "a91d4f60-2c3b-4e7a-8f15-0b6c9d2e4a71",
"branch_id": null,
"audience_id": "5f1c2a9e-8b7d-4c3e-9a61-2d4f7b0c1e83",
"settings": {
"channels": [
"email",
"linkedin"
],
"dailyLimit": 80,
"dailyEmailLimit": 80,
"dailyLinkedinLimit": 40,
"timezone": "Europe/London",
"workingHours": [
9,
18
],
"excludeWeekends": true,
"emailSenderMode": "client_owned",
"trackOpens": true,
"emailContent": {
"mode": "ai",
"prompt": "Short, specific, one question.",
"tone": "friendly",
"followUps": [
{
"delayDays": 3,
"prompt": "New angle: case study"
}
]
},
"linkedinContent": {
"mode": "ai",
"invite_tone": "friendly",
"followup_delay_minutes": 60
},
"selectedLinkedinAccountIds": [
"e4b8c2d1-7a3f-4b9e-8c1d-5f2a6e0b9c34"
],
"customInstructions": "Offer a 15-minute call. Use the formal form of address."
},
"sequence": [
{
"step": 1,
"delayDays": 0
},
{
"step": 2,
"delayDays": 3
}
],
"stats": {
"totalContacts": 412,
"emailsSent": 0,
"emailsOpened": 0,
"linksClicked": 0,
"repliesReceived": 0,
"meetingsBooked": 0
},
"created_at": "2026-10-26T09:12:44.000Z",
"updated_at": "2026-10-26T09:14:02.000Z"
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | A parameter has the wrong type, range or enum value. |
| 404 | not_found | Agent, branch, LinkedIn account, sender or custom audience is not in the workspace. |
| 409 | audience_not_ready | The custom audience is still processing. |
| 503 | provider_unavailable | senderEmailIds cannot be validated because the sender list is temporarily unavailable. |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: create_campaign
GET/v1/campaigns/:idGet campaign details + stats
(include_archived), POST, PATCH :id, launch, pause, pause-channel, resume-channel and archive moved to v2/campaigns.controller.ts (Round-6, 2026-10-26: allow-listed settings, ownership checks, clear 4xx for business failures, UI-identical launch). Full campaign: status, settings (all wizard fields), sequence, channel paused flags, channelStatuses, today's sends, stats.totalContacts. Read-only.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string | yes | Campaign id (UUID) from list_campaigns |
Request
curl -s "https://api.vendo-ai.com/v1/campaigns/$CAMPAIGN_ID" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: get_campaign
PATCH/v1/campaigns/:idUpdate campaign settings
Partial update of name, description and wizard settings (settings are merged). agentId and branchId can change only while the campaign is a draft. On a running campaign a change to email steps or delays is not applied once anything was sent; the pending change is visible as settings.smartleadSequencePending. Internal settings keys can no longer be written.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | Campaign id. |
name | body | string | New name. | |
description | body | string | ||
agentId | body | uuid | Draft only. | |
branchId | body | uuid|null | Draft only; null = General. | |
settings | body | object | Wizard settings (all optional, merged on update). channels [email|linkedin|phone_call]; dailyLimit (new contacts/day, ≤ sender capacity); dailyEmailLimit; dailyLinkedinLimit (campaign-wide invites/day across all accounts); timezone (IANA); workingHours [start 0–23, end 1–24]; excludeWeekends; campaignDurationDays; customInstructions (Communication Rules ≤6000: CTA, links, promises; also sets the ти/Ви, du/Sie form of address); emailSignature (HTML); notifyEmails[]; ccEmail; autoResponseEnabled; autoResponseDelayMinutes; followUpInterval & maxFollowUps (legacy, used when emailContent.followUps is empty); trackOpens; trackLinks; stopOnNegative; speedTier [conservative|balanced|aggressive]; emailSenderMode [managed|client_owned] (managed needs a paid plan to launch); clientEmailProvider [google|microsoft|null]; senderEmailIds[] (ids from GET /v1/email/senders; empty = auto-assign); emailContent {mode ai|fixed|html, prompt, tone formal|casual|friendly, variants ≤5 {subject, body}, htmlSteps ≤20 {subject, html, delayDays}, followUps ≤7 {delayDays 1–90, prompt | subject+body}}; linkedinContent {mode ai|fixed, invite_prompt, invite_tone, invite_exact_text ≤300, followup_delay_minutes 0–43200, followup_exact_text ≤1500}; selectedLinkedinAccountIds[] (ids from GET /v1/linkedin/accounts; empty = all connected at launch); linkedinAutoReplyDisabled. Other keys are ignored and listed in ignored_settings. |
Request
curl -s -X PATCH "https://api.vendo-ai.com/v1/campaigns/$CAMPAIGN_ID" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"settings": {"dailyLinkedinLimit": 30, "trackOpens": false}}'Response
Show the full response (67 lines)
{
"data": {
"id": "c3a7e2f4-1b5d-4e8a-9c0f-6d2b8a4e7f15",
"name": "UK SaaS founders — Q4",
"status": "draft",
"agent_id": "a91d4f60-2c3b-4e7a-8f15-0b6c9d2e4a71",
"branch_id": null,
"audience_id": "5f1c2a9e-8b7d-4c3e-9a61-2d4f7b0c1e83",
"settings": {
"channels": [
"email",
"linkedin"
],
"dailyLimit": 80,
"dailyEmailLimit": 80,
"dailyLinkedinLimit": 30,
"timezone": "Europe/London",
"workingHours": [
9,
18
],
"excludeWeekends": true,
"emailSenderMode": "client_owned",
"trackOpens": false,
"emailContent": {
"mode": "ai",
"prompt": "Short, specific, one question.",
"tone": "friendly",
"followUps": [
{
"delayDays": 3,
"prompt": "New angle: case study"
}
]
},
"linkedinContent": {
"mode": "ai",
"invite_tone": "friendly",
"followup_delay_minutes": 60
},
"selectedLinkedinAccountIds": [
"e4b8c2d1-7a3f-4b9e-8c1d-5f2a6e0b9c34"
],
"customInstructions": "Offer a 15-minute call. Use the formal form of address."
},
"sequence": [
{
"step": 1,
"delayDays": 0
},
{
"step": 2,
"delayDays": 3
}
],
"stats": {
"totalContacts": 412,
"emailsSent": 0,
"emailsOpened": 0,
"linksClicked": 0,
"repliesReceived": 0,
"meetingsBooked": 0
},
"created_at": "2026-10-26T09:12:44.000Z",
"updated_at": "2026-10-26T09:14:02.000Z"
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | A parameter has the wrong type, range or enum value. |
| 404 | not_found | Campaign does not exist or belongs to another workspace. |
| 409 | not_draft | agentId/branchId sent for a campaign that is no longer a draft. |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: update_campaign
DELETE/v1/campaigns/:idDelete campaign (requires confirm)
destructive. dry_run / confirm required. Permanently delete a campaign. NOT reversible. Only call after the user has reviewed preview_delete_campaign output and explicitly approved.
Protected: send {"dry_run": true} to preview, then {"confirm": true} to execute (protocol).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string | yes | |
dry_run | body | boolean | true = preview only (nothing happens) | |
confirm | body | boolean | true = execute. Send exactly one of dry_run / confirm |
Request
curl -s -X DELETE "https://api.vendo-ai.com/v1/campaigns/$CAMPAIGN_ID" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"dry_run": true}'Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
POST/v1/campaigns/:id/archiveArchive a campaign
Archives the campaign: status becomes completed, all outreach stops (an active campaign releases its mailboxes and LinkedIn accounts) and it is hidden from the default list (GET /v1/campaigns?include_archived=true shows it). Not reversible, duplicate to run it again. Fixed (2026-10-26): {"dry_run": true} was ignored and archived for real; it now returns a preview (status, channels, what stops, what is kept) and changes nothing. {"confirm": true} archives.
Protected: send {"dry_run": true} to preview, then {"confirm": true} to execute (protocol).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | Campaign id. |
dry_run | body | boolean | true = preview only: nothing is changed (HTTP 200) | |
confirm | body | boolean | true = execute. Send exactly one of dry_run / confirm |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/campaigns/$CAMPAIGN_ID/archive" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"dry_run": true}'Response
Show the full response (68 lines)
{
"data": {
"id": "c3a7e2f4-1b5d-4e8a-9c0f-6d2b8a4e7f15",
"name": "UK SaaS founders — Q4",
"status": "completed",
"agent_id": "a91d4f60-2c3b-4e7a-8f15-0b6c9d2e4a71",
"branch_id": null,
"audience_id": "5f1c2a9e-8b7d-4c3e-9a61-2d4f7b0c1e83",
"settings": {
"channels": [
"email",
"linkedin"
],
"dailyLimit": 80,
"dailyEmailLimit": 80,
"dailyLinkedinLimit": 40,
"timezone": "Europe/London",
"workingHours": [
9,
18
],
"excludeWeekends": true,
"emailSenderMode": "client_owned",
"trackOpens": true,
"emailContent": {
"mode": "ai",
"prompt": "Short, specific, one question.",
"tone": "friendly",
"followUps": [
{
"delayDays": 3,
"prompt": "New angle: case study"
}
]
},
"linkedinContent": {
"mode": "ai",
"invite_tone": "friendly",
"followup_delay_minutes": 60
},
"selectedLinkedinAccountIds": [
"e4b8c2d1-7a3f-4b9e-8c1d-5f2a6e0b9c34"
],
"customInstructions": "Offer a 15-minute call. Use the formal form of address."
},
"sequence": [
{
"step": 1,
"delayDays": 0
},
{
"step": 2,
"delayDays": 3
}
],
"stats": {
"totalContacts": 412,
"emailsSent": 0,
"emailsOpened": 0,
"linksClicked": 0,
"repliesReceived": 0,
"meetingsBooked": 0
},
"created_at": "2026-10-26T09:12:44.000Z",
"updated_at": "2026-10-26T09:14:02.000Z",
"is_archived": true
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | Campaign does not exist or belongs to another workspace. |
| 409 | already_archived | Already archived. |
| 400 | confirmation_required | Neither dry_run nor confirm was sent (or both) |
| 400 | invalid_request | Body has fields other than dry_run / confirm |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
POST/v1/campaigns/:id/duplicateDuplicate a campaign
#post-v1-campaigns-id-duplicate
Copies a campaign (settings + a new audience) into a new DRAFT named "<name> (Copy)". Nothing is sent, no credits. The backend copies company-based audiences only and no CRM branch; check the copy before launching.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | Campaign id. |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/campaigns/$CAMPAIGN_ID/duplicate" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Show the full response (67 lines)
{
"data": {
"id": "5f1c2a9e-8b7d-4c3e-9a61-2d4f7b0c1e83",
"name": "UK SaaS founders — Q4 (Copy)",
"status": "draft",
"agent_id": "a91d4f60-2c3b-4e7a-8f15-0b6c9d2e4a71",
"branch_id": null,
"audience_id": "5f1c2a9e-8b7d-4c3e-9a61-2d4f7b0c1e83",
"settings": {
"channels": [
"email",
"linkedin"
],
"dailyLimit": 80,
"dailyEmailLimit": 80,
"dailyLinkedinLimit": 40,
"timezone": "Europe/London",
"workingHours": [
9,
18
],
"excludeWeekends": true,
"emailSenderMode": "client_owned",
"trackOpens": true,
"emailContent": {
"mode": "ai",
"prompt": "Short, specific, one question.",
"tone": "friendly",
"followUps": [
{
"delayDays": 3,
"prompt": "New angle: case study"
}
]
},
"linkedinContent": {
"mode": "ai",
"invite_tone": "friendly",
"followup_delay_minutes": 60
},
"selectedLinkedinAccountIds": [
"e4b8c2d1-7a3f-4b9e-8c1d-5f2a6e0b9c34"
],
"customInstructions": "Offer a 15-minute call. Use the formal form of address."
},
"sequence": [
{
"step": 1,
"delayDays": 0
},
{
"step": 2,
"delayDays": 3
}
],
"stats": {
"totalContacts": 412,
"emailsSent": 0,
"emailsOpened": 0,
"linksClicked": 0,
"repliesReceived": 0,
"meetingsBooked": 0
},
"created_at": "2026-10-26T09:12:44.000Z",
"updated_at": "2026-10-26T09:14:02.000Z"
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | Campaign does not exist or belongs to another workspace. |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: duplicate_campaign
POST/v1/campaigns/:id/launchLaunch or resume a campaign
dry_run returns a readiness report computed without side effects: ready flag, blockers (AGENT_REQUIRED, AUDIENCE_REQUIRED, CLIENT_MAILBOX_NOT_CONNECTED, PAID_PLAN_REQUIRED for Vendo managed senders, INVALID_STATUS), warnings, the real recipient count, limits, sender mode, schedule and the LinkedIn accounts to activate. confirm does what the app does: starts the campaign, then queues LinkedIn on the selected (or all connected) accounts when LinkedIn is a channel and nothing is queued yet. A LinkedIn failure does not fail the launch. Real outreach starts in the working hours; credits are spent as messages go out. New blocker AUDIENCE_EMPTY (2026-10-26): the audience has 0 contacts, the dry run used to say ready: true although the launch then failed with 422.
Credits: Per message sent (email/LinkedIn per pricing) · Protected: send {"dry_run": true} to preview, then {"confirm": true} to execute (protocol).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | Campaign id. |
dry_run | body | boolean | true = preview only, nothing happens. | |
confirm | body | boolean | true = execute. Exactly one of dry_run / confirm is required. | |
linkedin_account_ids | body | uuid[] | Override the LinkedIn accounts to activate (default: settings.selectedLinkedinAccountIds, else all connected). |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/campaigns/$CAMPAIGN_ID/launch" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"dry_run": true}'Response
Show the full response (57 lines)
{
"data": {
"dry_run": true,
"would_do": "Launch campaign \"UK SaaS founders — Q4\"",
"ready": true,
"blockers": [],
"warnings": [
{
"code": "MAILBOXES_WARMING",
"message": "Your mailboxes are still warming up; sending starts as they become ready."
}
],
"campaign": {
"id": "c3a7e2f4-1b5d-4e8a-9c0f-6d2b8a4e7f15",
"name": "UK SaaS founders — Q4",
"status": "draft"
},
"recipient_count": 412,
"channels": [
"email",
"linkedin"
],
"agent_id": "a91d4f60-2c3b-4e7a-8f15-0b6c9d2e4a71",
"daily_limit": 80,
"daily_email_limit": 80,
"daily_linkedin_limit": 40,
"email_sender_mode": "client_owned",
"client_mailboxes": {
"usable": 2,
"ready": 0
},
"email_steps": 2,
"email_content_mode": "ai",
"track_opens": true,
"schedule": {
"timezone": "Europe/London",
"working_hours": [
9,
18
],
"exclude_weekends": true,
"duration_days": 30
},
"linkedin": {
"accounts": [
{
"id": "e4b8c2d1-7a3f-4b9e-8c1d-5f2a6e0b9c34",
"name": "Anna Clarke"
}
],
"source": "settings.selectedLinkedinAccountIds",
"already_queued": 0,
"daily_linkedin_limit": 40,
"auto_replies_disabled": false
}
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | confirmation_required | Neither dry_run nor confirm was sent. |
| 404 | not_found | Campaign does not exist or belongs to another workspace. |
| 409 | agent_required | confirm on a campaign without agent. |
| 409 | client_mailbox_not_connected | client_owned mode without a warming/ready own mailbox. |
| 403 | paid_plan_required | Managed senders on a free plan. |
| 409 | invalid_status | Status is not draft/paused/active. |
| 422 | audience_required | No audience. |
| 422 | launch_failed | Scheduling failed (e.g. no contacts with email and no LinkedIn queue); the campaign stays draft. |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
POST/v1/campaigns/:id/pausePause a campaign
Pauses every channel of an active campaign. Resume with launch. Business failures now answer 409 instead of 500.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | Campaign id. |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/campaigns/$CAMPAIGN_ID/pause" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Show the full response (67 lines)
{
"data": {
"id": "c3a7e2f4-1b5d-4e8a-9c0f-6d2b8a4e7f15",
"name": "UK SaaS founders — Q4",
"status": "paused",
"agent_id": "a91d4f60-2c3b-4e7a-8f15-0b6c9d2e4a71",
"branch_id": null,
"audience_id": "5f1c2a9e-8b7d-4c3e-9a61-2d4f7b0c1e83",
"settings": {
"channels": [
"email",
"linkedin"
],
"dailyLimit": 80,
"dailyEmailLimit": 80,
"dailyLinkedinLimit": 40,
"timezone": "Europe/London",
"workingHours": [
9,
18
],
"excludeWeekends": true,
"emailSenderMode": "client_owned",
"trackOpens": true,
"emailContent": {
"mode": "ai",
"prompt": "Short, specific, one question.",
"tone": "friendly",
"followUps": [
{
"delayDays": 3,
"prompt": "New angle: case study"
}
]
},
"linkedinContent": {
"mode": "ai",
"invite_tone": "friendly",
"followup_delay_minutes": 60
},
"selectedLinkedinAccountIds": [
"e4b8c2d1-7a3f-4b9e-8c1d-5f2a6e0b9c34"
],
"customInstructions": "Offer a 15-minute call. Use the formal form of address."
},
"sequence": [
{
"step": 1,
"delayDays": 0
},
{
"step": 2,
"delayDays": 3
}
],
"stats": {
"totalContacts": 412,
"emailsSent": 0,
"emailsOpened": 0,
"linksClicked": 0,
"repliesReceived": 0,
"meetingsBooked": 0
},
"created_at": "2026-10-26T09:12:44.000Z",
"updated_at": "2026-10-26T09:14:02.000Z"
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | Campaign does not exist or belongs to another workspace. |
| 409 | not_active | Campaign is not active. |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: pause_campaign
POST/v1/campaigns/:id/pause-channelPause one channel
#post-v1-campaigns-id-pause-channel
Pauses one channel of an active campaign; the others keep going. A missing or unknown channel is now a 400 (was HTTP 200 with an error body).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | Campaign id. |
channel | body | string | yes | Channel to pause. one of: email, linkedin, phone_call, voice_call |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/campaigns/$CAMPAIGN_ID/pause-channel" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"channel": "linkedin"}'Response
Show the full response (68 lines)
{
"data": {
"id": "c3a7e2f4-1b5d-4e8a-9c0f-6d2b8a4e7f15",
"name": "UK SaaS founders — Q4",
"status": "active",
"agent_id": "a91d4f60-2c3b-4e7a-8f15-0b6c9d2e4a71",
"branch_id": null,
"audience_id": "5f1c2a9e-8b7d-4c3e-9a61-2d4f7b0c1e83",
"settings": {
"channels": [
"email",
"linkedin"
],
"dailyLimit": 80,
"dailyEmailLimit": 80,
"dailyLinkedinLimit": 40,
"timezone": "Europe/London",
"workingHours": [
9,
18
],
"excludeWeekends": true,
"emailSenderMode": "client_owned",
"trackOpens": true,
"emailContent": {
"mode": "ai",
"prompt": "Short, specific, one question.",
"tone": "friendly",
"followUps": [
{
"delayDays": 3,
"prompt": "New angle: case study"
}
]
},
"linkedinContent": {
"mode": "ai",
"invite_tone": "friendly",
"followup_delay_minutes": 60
},
"selectedLinkedinAccountIds": [
"e4b8c2d1-7a3f-4b9e-8c1d-5f2a6e0b9c34"
],
"customInstructions": "Offer a 15-minute call. Use the formal form of address."
},
"sequence": [
{
"step": 1,
"delayDays": 0
},
{
"step": 2,
"delayDays": 3
}
],
"stats": {
"totalContacts": 412,
"emailsSent": 0,
"emailsOpened": 0,
"linksClicked": 0,
"repliesReceived": 0,
"meetingsBooked": 0
},
"created_at": "2026-10-26T09:12:44.000Z",
"updated_at": "2026-10-26T09:14:02.000Z",
"linkedin_channel_paused": true
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | A parameter has the wrong type, range or enum value. |
| 404 | not_found | Campaign does not exist or belongs to another workspace. |
| 409 | not_active | Campaign is not active. |
| 409 | already_paused | Channel already paused. |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: pause_channel
POST/v1/campaigns/:id/resume-channelResume one channel
#post-v1-campaigns-id-resume-channel
Resumes a paused channel. Campaigns auto-paused for insufficient credits stay paused until the balance is positive (400 from the backend).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | Campaign id. |
channel | body | string | yes | Channel to resume. one of: email, linkedin, phone_call, voice_call |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/campaigns/$CAMPAIGN_ID/resume-channel" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"channel": "linkedin"}'Response
Show the full response (68 lines)
{
"data": {
"id": "c3a7e2f4-1b5d-4e8a-9c0f-6d2b8a4e7f15",
"name": "UK SaaS founders — Q4",
"status": "active",
"agent_id": "a91d4f60-2c3b-4e7a-8f15-0b6c9d2e4a71",
"branch_id": null,
"audience_id": "5f1c2a9e-8b7d-4c3e-9a61-2d4f7b0c1e83",
"settings": {
"channels": [
"email",
"linkedin"
],
"dailyLimit": 80,
"dailyEmailLimit": 80,
"dailyLinkedinLimit": 40,
"timezone": "Europe/London",
"workingHours": [
9,
18
],
"excludeWeekends": true,
"emailSenderMode": "client_owned",
"trackOpens": true,
"emailContent": {
"mode": "ai",
"prompt": "Short, specific, one question.",
"tone": "friendly",
"followUps": [
{
"delayDays": 3,
"prompt": "New angle: case study"
}
]
},
"linkedinContent": {
"mode": "ai",
"invite_tone": "friendly",
"followup_delay_minutes": 60
},
"selectedLinkedinAccountIds": [
"e4b8c2d1-7a3f-4b9e-8c1d-5f2a6e0b9c34"
],
"customInstructions": "Offer a 15-minute call. Use the formal form of address."
},
"sequence": [
{
"step": 1,
"delayDays": 0
},
{
"step": 2,
"delayDays": 3
}
],
"stats": {
"totalContacts": 412,
"emailsSent": 0,
"emailsOpened": 0,
"linksClicked": 0,
"repliesReceived": 0,
"meetingsBooked": 0
},
"created_at": "2026-10-26T09:12:44.000Z",
"updated_at": "2026-10-26T09:14:02.000Z",
"linkedin_channel_paused": false
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | A parameter has the wrong type, range or enum value. |
| 404 | not_found | Campaign does not exist or belongs to another workspace. |
| 409 | not_paused | Channel is not paused. |
| 409 | invalid_status | Campaign is neither active nor paused. |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: resume_channel
POST/v1/campaigns/:id/sync-statsSync email counters
#post-v1-campaigns-id-sync-stats
Pulls sent/opened/clicked/replied/bounced counters from the sending provider and stores them. Live provider call: limited to once per minute per campaign.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | Campaign id. |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/campaigns/$CAMPAIGN_ID/sync-stats" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"synced": true,
"stats": {
"sent": 1260,
"opened": 512,
"clicked": 41,
"replied": 37,
"bounced": 9
},
"diff": {
"sent": 12,
"opened": 5,
"clicked": 0,
"replied": 1,
"bounced": 0
}
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | Campaign does not exist or belongs to another workspace. |
| 409 | not_sent_yet | Campaign never sent email. |
| 429 | rate_limited | Called again within 60 s. |
| 502 | provider_unavailable | Provider did not answer. |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: sync_campaign_stats
Wizard previews 3
POST/v1/campaigns/preview/emailPreview a campaign email
#post-v1-campaigns-preview-email
Renders the exact first email or follow-up #k a campaign would send, with its writing rules, tone, Communication Rules or fixed text. With campaign_id the saved settings are used and any field you pass overrides them. One AI call on our side; no sending, no credits. Test keys allowed.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
campaign_id | body | uuid | Use this campaign's saved settings and agent. | |
agent_id | body | uuid | Agent for AI mode (required without campaign_id). | |
mode | body | string | ai or fixed. one of: ai, fixed | |
prompt | body | string | AI writing rules (≤4000). | |
tone | body | string | one of: formal, casual, friendly | |
custom_instructions | body | string | Communication Rules (≤6000). | |
variant | body | object | Fixed mode {subject, body}; placeholders like {{first_name}} are filled. | |
follow_up | body | object | {index 1–7, delay_days 1–90, previous {subject, body}}, preview follow-up #index. | |
sample | body | object | {contact_name, company_name, position, city, country, website} sample recipient. |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/campaigns/preview/email" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"campaign_id": "c3a7e2f4-1b5d-4e8a-9c0f-6d2b8a4e7f15", "follow_up": {"index": 1}}'Response
{
"data": {
"mode": "ai",
"subject": "Re: quicker onboarding at Acme Corp",
"body": "Hi Alex,\n\nFollowing up on my note from Monday — one thing teams like yours found useful...",
"follow_up_index": 1
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | A parameter has the wrong type, range or enum value. |
| 404 | not_found | Campaign or agent not in the workspace. |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
POST/v1/campaigns/preview/linkedin-followupPreview the LinkedIn message after connect
#post-v1-campaigns-preview-linkedin-followup
Renders the first LinkedIn message sent after an invite is accepted: AI mode uses the agent's follow-up generator, fixed mode fills the placeholders of the fixed text. Returns the configured delay. No sending, no credits.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
campaign_id | body | uuid | ||
agent_id | body | uuid | ||
mode | body | string | one of: ai, fixed | |
exact_text | body | string | Fixed text (≤1500). | |
sample | body | object |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/campaigns/preview/linkedin-followup" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"campaign_id": "c3a7e2f4-1b5d-4e8a-9c0f-6d2b8a4e7f15"}'Response
{
"data": {
"mode": "ai",
"message": "Thanks for connecting, Alex! Quick question — how are you handling onboarding for new accounts today?",
"followup_delay_minutes": 60
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | A parameter has the wrong type, range or enum value. |
| 404 | not_found | Campaign or agent not in the workspace. |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
POST/v1/campaigns/preview/linkedin-invitePreview a LinkedIn invite
#post-v1-campaigns-preview-linkedin-invite
Renders the LinkedIn connection note (AI from rules/tone or fixed text with placeholders) and reports its length against the 200-character limit. No sending, no credits.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
campaign_id | body | uuid | ||
agent_id | body | uuid | Required for AI mode without campaign_id. | |
mode | body | string | one of: ai, fixed | |
prompt | body | string | Invite rules. | |
tone | body | string | one of: formal, casual, friendly | |
exact_text | body | string | Fixed note (≤300). | |
sample | body | object |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/campaigns/preview/linkedin-invite" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"campaign_id": "c3a7e2f4-1b5d-4e8a-9c0f-6d2b8a4e7f15"}'Response
{
"data": {
"mode": "ai",
"message": "Hi Alex, I work with growth teams at B2B SaaS companies in London — would be glad to connect.",
"charCount": 104,
"limit": 200,
"withinLimit": true
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | A parameter has the wrong type, range or enum value. |
| 404 | not_found | Campaign or agent not in the workspace. |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
Emails, replies & delivery 12
GET/v1/campaigns/emails/:emailIdOpen one email
#get-v1-campaigns-emails-emailid
One email event with content, recipient, related open/click/reply events, the reply and the conversation thread. reply is always present (null when the email has no reply).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
emailId | path | uuid | yes | Email event id from sent emails or replies. |
Request
curl -s "https://api.vendo-ai.com/v1/campaigns/emails/$EMAIL_ID" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Show the full response (46 lines)
{
"data": {
"id": "b2f6d8a4-9e1c-4f3b-a7d2-6c0e5b8f1a49",
"campaignId": "c3a7e2f4-1b5d-4e8a-9c0f-6d2b8a4e7f15",
"companyId": "5f1c2a9e-8b7d-4c3e-9a61-2d4f7b0c1e83",
"eventType": "sent",
"contactEmail": "james@brightlane.io",
"subject": "Quicker onboarding at Brightlane",
"bodyHtml": "<p>Hi James, ...</p>",
"bodyText": "Hi James, ...",
"contactName": "James Whitfield",
"companyName": "Brightlane",
"companyWebsite": "brightlane.io",
"sentAt": "2026-10-25T10:03:11.000Z",
"events": [
{
"type": "opened",
"timestamp": "2026-10-25T12:40:02.000Z"
},
{
"type": "replied",
"timestamp": "2026-10-26T11:02:00.000Z"
}
],
"reply": {
"subject": "Re: Quicker onboarding at Brightlane",
"body": "Sounds interesting — can you send a short deck?",
"from": "james@brightlane.io",
"receivedAt": "2026-10-26T11:02:00.000Z"
},
"conversation": [
{
"direction": "outbound",
"subject": "Quicker onboarding at Brightlane",
"timestamp": "2026-10-25T10:03:11.000Z",
"type": "initial"
},
{
"direction": "inbound",
"subject": "Re: Quicker onboarding at Brightlane",
"timestamp": "2026-10-26T11:02:00.000Z",
"type": "reply"
}
]
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | Email not found or not in the workspace. |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: get_campaign_email
POST/v1/campaigns/emails/:emailId/refresh-replyReload a reply
#post-v1-campaigns-emails-emailid-refresh-reply
Fetches the latest reply text for the email from the sending provider and stores it (use when a reply shows as empty). Throttled to once per 10 s per email.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
emailId | path | uuid | yes |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/campaigns/emails/$EMAIL_ID/refresh-reply" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"success": true,
"reply": {
"subject": "Re: Quicker onboarding at Brightlane",
"body": "Sounds interesting — can you send a short deck?",
"from": "james@brightlane.io",
"receivedAt": "2026-10-26T11:02:00.000Z"
}
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | Email not found. |
| 409 | not_sent_yet | Campaign has no provider campaign. |
| 409 | reply_not_available | No reply could be loaded. |
| 429 | rate_limited | Too frequent. |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
POST/v1/campaigns/emails/:emailId/replyReply to a lead
#post-v1-campaigns-emails-emailid-reply
Sends a manual reply in the lead's thread immediately via the sending provider, bypassing daily limits. REAL email; dry_run returns recipient, subject and the exact text without sending.
Protected: send {"dry_run": true} to preview, then {"confirm": true} to execute (protocol).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
emailId | path | uuid | yes | The replied email event id. |
body | body | string | yes | Reply body, HTML allowed (≤50000). |
dry_run | body | boolean | true = preview only, nothing happens. | |
confirm | body | boolean | true = execute. Exactly one of dry_run / confirm is required. |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/campaigns/emails/$EMAIL_ID/reply" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"confirm": true, "body": "<p>Hi James, here is the deck: ...</p>"}'Response
{
"data": {
"success": true,
"messageId": "<20261026114512.4471@mail.brightlane-outreach.com>"
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | confirmation_required | Neither dry_run nor confirm was sent. |
| 400 | invalid_request | A parameter has the wrong type, range or enum value. |
| 404 | not_found | Email not found. |
| 409 | not_sent_yet | No provider campaign to thread into. |
| 502 | send_failed | Provider rejected the reply; nothing was sent. |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: preview_reply_to_campaign_email, reply_to_campaign_email
GET/v1/campaigns/:id/email-leadsEngaged email leads
#get-v1-campaigns-id-email-leads
Recipients with open/click/reply counts read live from the sending provider. Throttled to one call per 15 s per campaign and key.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | Campaign id. |
filter | query | string | Default all. one of: opened, clicked, replied, all |
Request
curl -s "https://api.vendo-ai.com/v1/campaigns/$CAMPAIGN_ID/email-leads" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"leads": [
{
"id": "88213456",
"email": "james@brightlane.io",
"first_name": "James",
"last_name": "Whitfield",
"company_name": "Brightlane",
"website": "brightlane.io",
"status": "INPROGRESS",
"open_count": 3,
"click_count": 1,
"reply_count": 1,
"vendo_company_id": "5f1c2a9e-8b7d-4c3e-9a61-2d4f7b0c1e83"
}
],
"stats": {
"total": 1260,
"opened": 388,
"clicked": 33,
"replied": 31
}
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | A parameter has the wrong type, range or enum value. |
| 404 | not_found | Campaign does not exist or belongs to another workspace. |
| 429 | rate_limited | Called again within 15 s. |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
POST/v1/campaigns/:id/emails/preparePre-generate emails (legacy)
#post-v1-campaigns-id-emails-prepare
Generates emails for every contact ahead of sending. Works only for legacy company-based audiences; people and custom-audience campaigns prepare automatically after launch. Synchronous, can take up to 4 minutes. Nothing is sent.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | Campaign id. |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/campaigns/$CAMPAIGN_ID/emails/prepare" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"total": 120,
"validated": 118,
"generated": 116,
"failed": 4,
"durationMs": 84211,
"errors": []
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | Campaign does not exist or belongs to another workspace. |
| 409 | prepare_not_supported | Audience is not company-based. |
| 504 | timeout | Still running after 240 s; poll GET /emails/prepared. |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
GET/v1/campaigns/:id/emails/preparedPrepared emails
#get-v1-campaigns-id-emails-prepared
Pre-generated emails pipeline with status counts and the preparation progress (pending_validation, generating, ready_to_send, scheduled, queued_in_smartlead, sent, *_failed).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | Campaign id. |
status | query | string | Pipeline status. | |
limit | query | integer | Page size, 1–200 (default 50). | |
offset | query | integer | Rows to skip (default 0). |
Request
curl -s "https://api.vendo-ai.com/v1/campaigns/$CAMPAIGN_ID/emails/prepared" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"emails": [
{
"id": "5f1c2a9e-8b7d-4c3e-9a61-2d4f7b0c1e83",
"status": "ready_to_send",
"subject": "Onboarding at Northwind",
"contact_email": "sara@northwind.dev",
"created_at": "2026-10-26T08:00:00.000Z"
}
],
"total": 120,
"stats": {
"ready_to_send": 96,
"scheduled": 20,
"generation_failed": 4
},
"progress": {
"total": 120,
"validated": 116,
"generated": 116,
"failed": 4,
"isComplete": true
}
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | A parameter has the wrong type, range or enum value. |
| 404 | not_found | Campaign does not exist or belongs to another workspace. |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: list_campaign_emails
GET/v1/campaigns/:id/emails/queuedQueued emails
#get-v1-campaigns-id-emails-queued
Emails scheduled or queued to go out, with counts per state.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | Campaign id. |
search | query | string | ||
limit | query | integer | Page size, 1–200 (default 50). | |
offset | query | integer | Rows to skip (default 0). |
Request
curl -s "https://api.vendo-ai.com/v1/campaigns/$CAMPAIGN_ID/emails/queued" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"emails": [
{
"id": "5f1c2a9e-8b7d-4c3e-9a61-2d4f7b0c1e83",
"companyName": "Northwind Labs",
"contactEmail": "sara@northwind.dev",
"subject": "Onboarding at Northwind",
"bodyPreview": "Hi Sara, ...",
"status": "scheduled",
"scheduledAt": "2026-10-27T09:20:00.000Z"
}
],
"total": 44,
"stats": {
"scheduled": 40,
"queued": 4
}
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | A parameter has the wrong type, range or enum value. |
| 404 | not_found | Campaign does not exist or belongs to another workspace. |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: list_campaign_emails
POST/v1/campaigns/:id/emails/retry-preparationRetry failed preparation
#post-v1-campaigns-id-emails-retry-preparation
Puts prepared emails that failed validation or generation back in the queue (max 3 attempts per email). Nothing is sent.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | Campaign id. |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/campaigns/$CAMPAIGN_ID/emails/retry-preparation" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"success": true,
"retriedCount": 4
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | Campaign does not exist or belongs to another workspace. |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
POST/v1/campaigns/:id/emails/schedule-deliverySchedule prepared emails
#post-v1-campaigns-id-emails-schedule-delivery
Spreads all ready prepared emails over the campaign's working-hour slots within the daily limit; they are then sent for real automatically. dry_run reports how many are ready.
Credits: Per email sent · Protected: send {"dry_run": true} to preview, then {"confirm": true} to execute (protocol).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | Campaign id. |
dry_run | body | boolean | true = preview only, nothing happens. | |
confirm | body | boolean | true = execute. Exactly one of dry_run / confirm is required. |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/campaigns/$CAMPAIGN_ID/emails/schedule-delivery" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"confirm": true}'Response
{
"data": {
"scheduled": 96,
"slots": [
{
"date": "2026-10-27",
"count": 80
},
{
"date": "2026-10-28",
"count": 16
}
]
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | confirmation_required | Neither dry_run nor confirm was sent. |
| 404 | not_found | Campaign does not exist or belongs to another workspace. |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: preview_schedule_campaign_delivery, schedule_campaign_delivery
POST/v1/campaigns/:id/emails/send-nowSend prepared emails now
#post-v1-campaigns-id-emails-send-now
Sends up to limit ready/scheduled prepared emails immediately, outside the schedule. REAL emails. dry_run reports how many would go.
Credits: Per email sent · Protected: send {"dry_run": true} to preview, then {"confirm": true} to execute (protocol).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | Campaign id. |
dry_run | body | boolean | true = preview only, nothing happens. | |
confirm | body | boolean | true = execute. Exactly one of dry_run / confirm is required. | |
limit | body | integer | 1–100 (default 10). |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/campaigns/$CAMPAIGN_ID/emails/send-now" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"confirm": true, "limit": 5}'Response
{
"data": {
"sent": 5,
"failed": 0
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | confirmation_required | Neither dry_run nor confirm was sent. |
| 400 | invalid_request | A parameter has the wrong type, range or enum value. |
| 404 | not_found | Campaign does not exist or belongs to another workspace. |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: preview_send_campaign_emails_now, send_campaign_emails_now
GET/v1/campaigns/:id/emails/sentSent emails
#get-v1-campaigns-id-emails-sent
Emails already sent by the campaign, newest first, filterable by status and searchable by address.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | Campaign id. |
status | query | string | one of: sent, delivered, opened, clicked, replied, bounced | |
search | query | string | Email address contains. | |
limit | query | integer | Page size, 1–200 (default 50). | |
offset | query | integer | Rows to skip (default 0). |
Request
curl -s "https://api.vendo-ai.com/v1/campaigns/$CAMPAIGN_ID/emails/sent" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"emails": [
{
"id": "b2f6d8a4-9e1c-4f3b-a7d2-6c0e5b8f1a49",
"campaignId": "c3a7e2f4-1b5d-4e8a-9c0f-6d2b8a4e7f15",
"companyId": "5f1c2a9e-8b7d-4c3e-9a61-2d4f7b0c1e83",
"contactEmail": "james@brightlane.io",
"subject": "Quicker onboarding at Brightlane",
"bodyPreview": "Hi James, noticed Brightlane...",
"sentAt": "2026-10-25T10:03:11.000Z",
"status": "opened",
"openedAt": "2026-10-25T12:40:02.000Z",
"clickedAt": null,
"repliedAt": null,
"companyName": "Brightlane"
}
],
"total": 1260
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | A parameter has the wrong type, range or enum value. |
| 404 | not_found | Campaign does not exist or belongs to another workspace. |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: list_campaign_emails
GET/v1/campaigns/:id/repliesReplies inbox
Email replies of the campaign with body and classification: real, auto_reply (out of office) or bounce. Up to 500, newest first.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | Campaign id. |
type | query | string | one of: real, auto_reply, bounce |
Request
curl -s "https://api.vendo-ai.com/v1/campaigns/$CAMPAIGN_ID/replies" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": [
{
"id": "b2f6d8a4-9e1c-4f3b-a7d2-6c0e5b8f1a49",
"received_at": "2026-10-26T11:02:00.000Z",
"from": "james@brightlane.io",
"lead_email": "james@brightlane.io",
"subject": "Re: Quicker onboarding at Brightlane",
"body": "Sounds interesting — can you send a short deck?",
"reply_type": "real",
"company_domain": "brightlane.io"
}
],
"total": 1
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | A parameter has the wrong type, range or enum value. |
| 404 | not_found | Campaign does not exist or belongs to another workspace. |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
AI conversations (threads) 6
GET/v1/campaigns/threads/:threadIdOpen a conversation
#get-v1-campaigns-threads-threadid
One conversation with all AI, human and contact messages. sentiment is always present (null until the thread is classified).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
threadId | path | uuid | yes |
Request
curl -s "https://api.vendo-ai.com/v1/campaigns/threads/$THREAD_ID" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Show the full response (32 lines)
{
"data": {
"id": "7d2e9f1a-4c6b-4a8e-b3d5-1f0c8e2a6b97",
"campaign_id": "c3a7e2f4-1b5d-4e8a-9c0f-6d2b8a4e7f15",
"company_id": "5f1c2a9e-8b7d-4c3e-9a61-2d4f7b0c1e83",
"contact_email": "james@brightlane.io",
"contact_name": "James Whitfield",
"status": "ai_handling",
"sentiment": "positive",
"message_count": 3,
"last_message_at": "2026-10-26T11:20:00.000Z",
"messages": [
{
"id": "5f1c2a9e-8b7d-4c3e-9a61-2d4f7b0c1e83",
"direction": "inbound",
"from": "james@brightlane.io",
"subject": "Re: Quicker onboarding",
"body_text": "Can you send a deck?",
"sent_by": "contact",
"created_at": "2026-10-26T11:02:00.000Z"
},
{
"id": "b2f6d8a4-9e1c-4f3b-a7d2-6c0e5b8f1a49",
"direction": "outbound",
"subject": "Re: Quicker onboarding",
"body_text": "Sure — attached. Would Thursday work?",
"sent_by": "ai",
"created_at": "2026-10-26T11:20:00.000Z"
}
]
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | Thread not found or not the key user's. |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: get_campaign_thread
POST/v1/campaigns/threads/:threadId/closeClose a conversation
#post-v1-campaigns-threads-threadid-close
Marks the conversation closed; the AI no longer replies. Nothing is sent.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
threadId | path | uuid | yes |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/campaigns/threads/$THREAD_ID/close" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Show the full response (32 lines)
{
"data": {
"id": "7d2e9f1a-4c6b-4a8e-b3d5-1f0c8e2a6b97",
"campaign_id": "c3a7e2f4-1b5d-4e8a-9c0f-6d2b8a4e7f15",
"company_id": "5f1c2a9e-8b7d-4c3e-9a61-2d4f7b0c1e83",
"contact_email": "james@brightlane.io",
"contact_name": "James Whitfield",
"status": "closed",
"sentiment": "positive",
"message_count": 3,
"last_message_at": "2026-10-26T11:20:00.000Z",
"messages": [
{
"id": "5f1c2a9e-8b7d-4c3e-9a61-2d4f7b0c1e83",
"direction": "inbound",
"from": "james@brightlane.io",
"subject": "Re: Quicker onboarding",
"body_text": "Can you send a deck?",
"sent_by": "contact",
"created_at": "2026-10-26T11:02:00.000Z"
},
{
"id": "b2f6d8a4-9e1c-4f3b-a7d2-6c0e5b8f1a49",
"direction": "outbound",
"subject": "Re: Quicker onboarding",
"body_text": "Sure — attached. Would Thursday work?",
"sent_by": "ai",
"created_at": "2026-10-26T11:20:00.000Z"
}
]
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | Thread not found. |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
POST/v1/campaigns/threads/:threadId/replyHuman reply in a conversation
#post-v1-campaigns-threads-threadid-reply
Sends a REAL email to the contact inside the conversation. subject defaults to "Re: <last subject>", body_html defaults to the text as paragraphs. dry_run shows the exact email without sending.
Protected: send {"dry_run": true} to preview, then {"confirm": true} to execute (protocol).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
threadId | path | uuid | yes | |
body | body | string | yes | Plain text (≤20000). |
subject | body | string | ||
body_html | body | string | ||
dry_run | body | boolean | true = preview only, nothing happens. | |
confirm | body | boolean | true = execute. Exactly one of dry_run / confirm is required. |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/campaigns/threads/$THREAD_ID/reply" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"confirm": true, "body": "Hi James, Thursday 3pm works. Talk then!"}'Response
Show the full response (32 lines)
{
"data": {
"id": "7d2e9f1a-4c6b-4a8e-b3d5-1f0c8e2a6b97",
"campaign_id": "c3a7e2f4-1b5d-4e8a-9c0f-6d2b8a4e7f15",
"company_id": "5f1c2a9e-8b7d-4c3e-9a61-2d4f7b0c1e83",
"contact_email": "james@brightlane.io",
"contact_name": "James Whitfield",
"status": "ai_handling",
"sentiment": "positive",
"message_count": 4,
"last_message_at": "2026-10-26T11:20:00.000Z",
"messages": [
{
"id": "5f1c2a9e-8b7d-4c3e-9a61-2d4f7b0c1e83",
"direction": "inbound",
"from": "james@brightlane.io",
"subject": "Re: Quicker onboarding",
"body_text": "Can you send a deck?",
"sent_by": "contact",
"created_at": "2026-10-26T11:02:00.000Z"
},
{
"id": "b2f6d8a4-9e1c-4f3b-a7d2-6c0e5b8f1a49",
"direction": "outbound",
"subject": "Re: Quicker onboarding",
"body_text": "Sure — attached. Would Thursday work?",
"sent_by": "ai",
"created_at": "2026-10-26T11:20:00.000Z"
}
]
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | confirmation_required | Neither dry_run nor confirm was sent. |
| 400 | invalid_request | A parameter has the wrong type, range or enum value. |
| 404 | not_found | Thread not found. |
| 502 | send_failed | Email could not be sent. |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: preview_reply_in_campaign_thread, reply_in_campaign_thread
POST/v1/campaigns/threads/:threadId/takeoverTake over a conversation
#post-v1-campaigns-threads-threadid-takeover
Stops the AI from replying in this conversation; a human continues. Nothing is sent.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
threadId | path | uuid | yes |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/campaigns/threads/$THREAD_ID/takeover" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Show the full response (32 lines)
{
"data": {
"id": "7d2e9f1a-4c6b-4a8e-b3d5-1f0c8e2a6b97",
"campaign_id": "c3a7e2f4-1b5d-4e8a-9c0f-6d2b8a4e7f15",
"company_id": "5f1c2a9e-8b7d-4c3e-9a61-2d4f7b0c1e83",
"contact_email": "james@brightlane.io",
"contact_name": "James Whitfield",
"status": "human_takeover",
"sentiment": "positive",
"message_count": 3,
"last_message_at": "2026-10-26T11:20:00.000Z",
"messages": [
{
"id": "5f1c2a9e-8b7d-4c3e-9a61-2d4f7b0c1e83",
"direction": "inbound",
"from": "james@brightlane.io",
"subject": "Re: Quicker onboarding",
"body_text": "Can you send a deck?",
"sent_by": "contact",
"created_at": "2026-10-26T11:02:00.000Z"
},
{
"id": "b2f6d8a4-9e1c-4f3b-a7d2-6c0e5b8f1a49",
"direction": "outbound",
"subject": "Re: Quicker onboarding",
"body_text": "Sure — attached. Would Thursday work?",
"sent_by": "ai",
"created_at": "2026-10-26T11:20:00.000Z"
}
]
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | Thread not found. |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
GET/v1/campaigns/:id/threadsAI email conversations
Conversations the AI handles for the campaign, filterable by status and sentiment. Only threads of the API key's user are visible (same as the app).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | Campaign id. |
status | query | string | one of: ai_handling, human_takeover, closed | |
sentiment | query | string | one of: positive, objection, referral, unsubscribe, neutral | |
limit | query | integer | Page size, 1–200 (default 50). | |
offset | query | integer | Rows to skip (default 0). |
Request
curl -s "https://api.vendo-ai.com/v1/campaigns/$CAMPAIGN_ID/threads" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Show the full response (37 lines)
{
"data": {
"threads": [
{
"id": "7d2e9f1a-4c6b-4a8e-b3d5-1f0c8e2a6b97",
"campaign_id": "c3a7e2f4-1b5d-4e8a-9c0f-6d2b8a4e7f15",
"company_id": "5f1c2a9e-8b7d-4c3e-9a61-2d4f7b0c1e83",
"contact_email": "james@brightlane.io",
"contact_name": "James Whitfield",
"status": "ai_handling",
"sentiment": "positive",
"message_count": 3,
"last_message_at": "2026-10-26T11:20:00.000Z",
"messages": [
{
"id": "5f1c2a9e-8b7d-4c3e-9a61-2d4f7b0c1e83",
"direction": "inbound",
"from": "james@brightlane.io",
"subject": "Re: Quicker onboarding",
"body_text": "Can you send a deck?",
"sent_by": "contact",
"created_at": "2026-10-26T11:02:00.000Z"
},
{
"id": "b2f6d8a4-9e1c-4f3b-a7d2-6c0e5b8f1a49",
"direction": "outbound",
"subject": "Re: Quicker onboarding",
"body_text": "Sure — attached. Would Thursday work?",
"sent_by": "ai",
"created_at": "2026-10-26T11:20:00.000Z"
}
]
}
],
"total": 31
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | A parameter has the wrong type, range or enum value. |
| 404 | not_found | Campaign does not exist or belongs to another workspace. |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
GET/v1/campaigns/:id/threads/statsConversation counters
#get-v1-campaigns-id-threads-stats
Counts of the campaign's conversations by status and by sentiment.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | Campaign id. |
Request
curl -s "https://api.vendo-ai.com/v1/campaigns/$CAMPAIGN_ID/threads/stats" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"byStatus": {
"ai_handling": 18,
"human_takeover": 7,
"closed": 6,
"total": 31
},
"bySentiment": {
"positive": 12,
"objection": 9,
"referral": 2,
"unsubscribe": 3,
"neutral": 5
}
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | Campaign does not exist or belongs to another workspace. |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
LinkedIn accounts, conversations & campaigns 14
GET/v1/campaigns/:id/linkedin/accountsAccounts working a campaign
#get-v1-campaigns-id-linkedin-accounts
LinkedIn accounts working this campaign with per-account counters.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | Campaign id. |
Request
curl -s "https://api.vendo-ai.com/v1/campaigns/$CAMPAIGN_ID/linkedin/accounts" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"accounts": [
{
"account": {
"id": "e4b8c2d1-7a3f-4b9e-8c1d-5f2a6e0b9c34",
"linkedin_name": "Anna Clarke",
"status": "ok"
},
"outreachCount": 206,
"pending": 94,
"inviteSent": 71,
"connected": 25,
"messageSent": 12,
"replied": 4
}
]
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | Campaign does not exist or belongs to another workspace. |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
POST/v1/campaigns/:id/linkedin/accountsAdd an account to a campaign
#post-v1-campaigns-id-linkedin-accounts
Adds a connected account to a running campaign: pending contacts are rebalanced across accounts and backfilled. Invites from it start in the next cycles.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | Campaign id. |
account_id | body | uuid | yes | LinkedIn account id. |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/campaigns/$CAMPAIGN_ID/linkedin/accounts" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"account_id": "e4b8c2d1-7a3f-4b9e-8c1d-5f2a6e0b9c34"}'Response
{
"data": {
"rebalanced": 47,
"pinned": 0,
"distribution": {
"e4b8c2d1-7a3f-4b9e-8c1d-5f2a6e0b9c34": 94
},
"alreadyAttached": false,
"backfillCreated": 12,
"backfillSkippedAllAccountsTried": 0,
"backfillSkippedAlreadyConverting": 3,
"audienceLinkedinTotal": 388
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | A parameter has the wrong type, range or enum value. |
| 404 | not_found | Campaign or account not in the workspace. |
| 409 | account_not_connected | Account status is not ok. |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
DELETE/v1/campaigns/:id/linkedin/accounts/:accountIdRemove an account from a campaign
#delete-v1-campaigns-id-linkedin-accounts-accountid
Stops using the account for this campaign; its pending contacts move to the remaining accounts. Sent invites stay.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | Campaign id. |
accountId | path | uuid | yes |
Request
curl -s -X DELETE "https://api.vendo-ai.com/v1/campaigns/$CAMPAIGN_ID/linkedin/accounts/$ACCOUNT_ID" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"removed": true,
"redistributed": 94
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | Campaign or account not in the workspace. |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
POST/v1/campaigns/:id/linkedin/activateActivate LinkedIn on a campaign
#post-v1-campaigns-id-linkedin-activate
Queues the campaign audience for LinkedIn on the given accounts (default: settings.selectedLinkedinAccountIds, else all connected) and adds linkedin to the channels. On an active campaign real connection invites start within the next cycles.
Credits: Per invite/message sent · Protected: send {"dry_run": true} to preview, then {"confirm": true} to execute (protocol).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | Campaign id. |
account_ids | body | uuid[] | Accounts with status ok. | |
dry_run | body | boolean | true = preview only, nothing happens. | |
confirm | body | boolean | true = execute. Exactly one of dry_run / confirm is required. |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/campaigns/$CAMPAIGN_ID/linkedin/activate" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"confirm": true}'Response
{
"data": {
"total": 412,
"withLinkedin": 388,
"created": 388,
"distribution": {
"e4b8c2d1-7a3f-4b9e-8c1d-5f2a6e0b9c34": 194,
"5f1c2a9e-8b7d-4c3e-9a61-2d4f7b0c1e83": 194
},
"skippedAlreadyConverting": 0,
"skippedAllAccountsTried": 0
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | confirmation_required | Neither dry_run nor confirm was sent. |
| 400 | invalid_request | A parameter has the wrong type, range or enum value. |
| 404 | not_found | Campaign or account not in the workspace. |
| 409 | no_linkedin_accounts | No connected account. |
| 409 | account_not_connected | A given account is not ok. |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: preview_activate_campaign_linkedin, activate_campaign_linkedin
GET/v1/campaigns/:id/linkedin/outreachLinkedIn queue of a campaign
#get-v1-campaigns-id-linkedin-outreach
Contacts in the campaign's LinkedIn queue with status, optionally for one account.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | Campaign id. |
status | query | string | one of: pending, invite_sent, connected, message_sent, replied, failed, skipped | |
account_id | query | uuid | ||
limit | query | integer | Page size, 1–200 (default 50). | |
offset | query | integer | Rows to skip (default 0). |
Request
curl -s "https://api.vendo-ai.com/v1/campaigns/$CAMPAIGN_ID/linkedin/outreach" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"items": [
{
"id": "5f1c2a9e-8b7d-4c3e-9a61-2d4f7b0c1e83",
"campaign_id": "c3a7e2f4-1b5d-4e8a-9c0f-6d2b8a4e7f15",
"linkedin_account_id": "e4b8c2d1-7a3f-4b9e-8c1d-5f2a6e0b9c34",
"contact_name": "James Whitfield",
"company_name": "Brightlane",
"contact_linkedin_url": "https://www.linkedin.com/in/jwhitfield",
"status": "connected",
"human_takeover": false,
"invite_sent_at": "2026-10-24T09:10:00.000Z"
}
],
"total": 388
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | A parameter has the wrong type, range or enum value. |
| 404 | not_found | Campaign does not exist or belongs to another workspace. |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
GET/v1/campaigns/:id/linkedin/statsLinkedIn stats of a campaign
#get-v1-campaigns-id-linkedin-stats
Status counters, invites sent today, effective daily limit (min of account warm-up caps and dailyLinkedinLimit) and active accounts.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | Campaign id. |
account_id | query | uuid |
Request
curl -s "https://api.vendo-ai.com/v1/campaigns/$CAMPAIGN_ID/linkedin/stats" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"total": 388,
"pending": 188,
"inviteSent": 124,
"connected": 40,
"messageSent": 25,
"replied": 11,
"failed": 0,
"todayInviteSent": 22,
"dailyLimit": 40,
"activeAccounts": 2
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | A parameter has the wrong type, range or enum value. |
| 404 | not_found | Campaign does not exist or belongs to another workspace. |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
GET/v1/linkedin/accountsLinkedIn accounts
Connected LinkedIn accounts and their status. Always { role, accounts }. Without detailed: id, linkedin_name, status and the basic account fields; role is null. detailed=true returns the Settings › LinkedIn view: the caller's role, owner, campaigns and SDR searches using each account, 7-day activity, prospecting caps, warm-up stage, premium tier and proxy country (members see only accounts they connected). Fixed (2026-10-26): the plain list was a bare array and exposed the provider account id (unipile_account_id); provider ids are never returned. Example without detailed: {"data": {"role": null, "accounts": [{"id": "e4b8c2d1-…", "linkedin_name": "Anna Clarke", "status": "ok", …}]}}.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
detailed | query | boolean |
Request
curl -s "https://api.vendo-ai.com/v1/linkedin/accounts" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Show the full response (43 lines)
{
"data": {
"role": "OWNER",
"accounts": [
{
"id": "e4b8c2d1-7a3f-4b9e-8c1d-5f2a6e0b9c34",
"linkedin_name": "Anna Clarke",
"status": "ok",
"proxy_country": "GB",
"owner_user_id": "5f1c2a9e-8b7d-4c3e-9a61-2d4f7b0c1e83",
"owner_role": "OWNER",
"is_mine": true,
"campaigns": [
{
"id": "c3a7e2f4-1b5d-4e8a-9c0f-6d2b8a4e7f15",
"name": "UK SaaS founders — Q4",
"status": "active"
}
],
"sdr_searches": [],
"activity": {
"sent_last_7d": 182,
"last_activity": "2026-10-26T13:20:00.000Z"
},
"prospecting": {
"searches_today": 3,
"profile_views_today": 41,
"age_days": 210,
"warm": true,
"stage": "full",
"search_cap": 10,
"profile_view_cap": 80,
"invite_cap": 40
},
"premium": {
"tier": "sales_navigator",
"label": "Sales Navigator"
},
"invite_notes_blocked_until": null
}
]
}
}Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
POST/v1/linkedin/accounts/connectReturns a hosted URL a human must open in a browser to authorise an account. Optional campaignId.
#post-v1-linkedin-accounts-connect
Returns a hosted URL that a HUMAN must open in a browser to authorise a LinkedIn account. This tool cannot complete the connection itself, give the link to the user. No account exists until they finish the flow.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
campaign_id | body | string | Optionally bind the new account to a campaign |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/linkedin/accounts/connect" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
GET/v1/linkedin/accounts/:accountId/preview-messagesSample LinkedIn messages
#get-v1-linkedin-accounts-accountid-preview-messages
"Show sample messages": sample invite, follow-up and auto-reply plus suggested pitch, signature and contact info for the account in a campaign, built from the campaign's agent. AI calls on our side; no credits.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
accountId | path | uuid | yes | |
campaign_id | query | uuid | yes |
Request
curl -s "https://api.vendo-ai.com/v1/linkedin/accounts/$ACCOUNT_ID/preview-messages?campaign_id=$CAMPAIGN_ID" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"agentName": "Anna",
"companyName": "Brightlane",
"companyDescription": "Onboarding automation for B2B SaaS.",
"sampleInvite": "Hi Alex, ...",
"sampleFollowup": "Thanks for connecting, Alex! ...",
"sampleAutoReply": "Great question — ...",
"suggestedPitch": "We cut onboarding time by 40%.",
"suggestedInvitePitch": "Onboarding automation for SaaS",
"suggestedSignature": "Anna\nBrightlane",
"suggestedContactInfo": "anna@brightlane.io"
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | A parameter has the wrong type, range or enum value. |
| 404 | not_found | Account or campaign not in the workspace. |
| 409 | agent_required | Campaign has no agent. |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
GET/v1/linkedin/accounts/:accountId/settingsLinkedIn account settings
#get-v1-linkedin-accounts-accountid-settings
Per-account settings shared by every campaign and SDR process that uses the account: AI auto-replies, invites, follow-ups and signature. The first read creates the defaults, pre-filled from campaign_id's agent.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
accountId | path | uuid | yes | |
campaign_id | query | uuid | Pre-fill defaults from this campaign's agent. |
Request
curl -s "https://api.vendo-ai.com/v1/linkedin/accounts/$ACCOUNT_ID/settings" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Show the full response (36 lines)
{
"data": {
"id": "5f1c2a9e-8b7d-4c3e-9a61-2d4f7b0c1e83",
"user_id": "5f1c2a9e-8b7d-4c3e-9a61-2d4f7b0c1e83",
"linkedin_account_id": "e4b8c2d1-7a3f-4b9e-8c1d-5f2a6e0b9c34",
"enabled": true,
"tone": "friendly",
"custom_instructions": null,
"product_pitch": "We cut onboarding time for B2B SaaS by 40%.",
"calendly_url": "https://calendly.com/anna-clarke/15min",
"reply_delay_min_minutes": 2,
"reply_delay_max_minutes": 7,
"daily_reply_limit": 50,
"working_hours": {
"start": 9,
"end": 18
},
"timezone": "Europe/London",
"agent_id": "a91d4f60-2c3b-4e7a-8f15-0b6c9d2e4a71",
"contact_info": null,
"invites_enabled": true,
"invite_tone": "friendly",
"invite_pitch": "We cut onboarding time for B2B SaaS...",
"invite_custom_instructions": null,
"invite_cta": null,
"followup_enabled": true,
"followup_pitch": "We cut onboarding time for B2B SaaS by 40%.",
"followup_custom_instructions": null,
"followup_cta": "15-minute call this week?",
"signature_enabled": false,
"signature_text": "Anna Clarke\nBrightlane",
"signature_mode": "first_message",
"created_at": "2026-10-20T09:00:00.000Z",
"updated_at": "2026-10-26T09:00:00.000Z"
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | Account or campaign not in the workspace. |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
PATCH/v1/linkedin/accounts/:accountId/settingsUpdate LinkedIn account settings
#patch-v1-linkedin-accounts-accountid-settings
Changes per-account settings (send only changed fields; sending back id/user_id from the GET answers 400 forbidden_field). Applies to all campaigns and SDR processes using the account; to switch AI replies off for one campaign use settings.linkedinAutoReplyDisabled on the campaign.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
accountId | path | uuid | yes | |
enabled | body | boolean | AI auto-replies on/off. | |
tone | body | string | formal|casual|friendly | |
custom_instructions | body | string | ||
product_pitch | body | string | ||
calendly_url | body | string | Booking link. | |
reply_delay_min_minutes | body | integer | 0–1440 | |
reply_delay_max_minutes | body | integer | 0–1440 | |
daily_reply_limit | body | integer | 0–500 | |
working_hours | body | object | {start, end} | |
timezone | body | string | ||
agent_id | body | uuid|null | Agent used for replies. | |
contact_info | body | string | ||
invites_enabled | body | boolean | ||
invite_tone | body | string | ||
invite_pitch | body | string | ||
invite_custom_instructions | body | string | ||
invite_cta | body | string | ||
followup_enabled | body | boolean | ||
followup_pitch | body | string | ||
followup_custom_instructions | body | string | ||
followup_cta | body | string | ||
signature_enabled | body | boolean | ||
signature_text | body | string | ||
signature_mode | body | string | first_message|every_message |
Request
curl -s -X PATCH "https://api.vendo-ai.com/v1/linkedin/accounts/$ACCOUNT_ID/settings" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"enabled": true,
"calendly_url": "https://calendly.com/anna-clarke/15min",
"signature_enabled": true,
"signature_mode": "first_message"
}'Response
Show the full response (36 lines)
{
"data": {
"id": "5f1c2a9e-8b7d-4c3e-9a61-2d4f7b0c1e83",
"user_id": "5f1c2a9e-8b7d-4c3e-9a61-2d4f7b0c1e83",
"linkedin_account_id": "e4b8c2d1-7a3f-4b9e-8c1d-5f2a6e0b9c34",
"enabled": true,
"tone": "friendly",
"custom_instructions": null,
"product_pitch": "We cut onboarding time for B2B SaaS by 40%.",
"calendly_url": "https://calendly.com/anna-clarke/15min",
"reply_delay_min_minutes": 2,
"reply_delay_max_minutes": 7,
"daily_reply_limit": 50,
"working_hours": {
"start": 9,
"end": 18
},
"timezone": "Europe/London",
"agent_id": "a91d4f60-2c3b-4e7a-8f15-0b6c9d2e4a71",
"contact_info": null,
"invites_enabled": true,
"invite_tone": "friendly",
"invite_pitch": "We cut onboarding time for B2B SaaS...",
"invite_custom_instructions": null,
"invite_cta": null,
"followup_enabled": true,
"followup_pitch": "We cut onboarding time for B2B SaaS by 40%.",
"followup_custom_instructions": null,
"followup_cta": "15-minute call this week?",
"signature_enabled": true,
"signature_text": "Anna Clarke\nBrightlane",
"signature_mode": "first_message",
"created_at": "2026-10-20T09:00:00.000Z",
"updated_at": "2026-10-26T09:00:00.000Z"
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | A parameter has the wrong type, range or enum value. |
| 400 | forbidden_field | Body contains id/user_id etc. |
| 404 | not_found | Account or agent not in the workspace. |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
DELETE/v1/linkedin/accounts/:idDisconnect a LinkedIn account
#delete-v1-linkedin-accounts-id
Disconnects the account: every campaign and SDR search sending from it stops immediately and sending capacity drops by roughly 70–80 invites/day. Destructive protocol. Fixed (2026-10-26): a foreign or unknown id answers 404 also for dry_run (it used to preview any id); the preview names the account and where it is used.
Protected: send {"dry_run": true} to preview, then {"confirm": true} to execute (protocol).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | LinkedIn account id from GET /v1/linkedin/accounts |
dry_run | body | boolean | true = preview only: nothing is changed (HTTP 200) | |
confirm | body | boolean | true = execute. Send exactly one of dry_run / confirm |
Request
curl -s -X DELETE "https://api.vendo-ai.com/v1/linkedin/accounts/$ACCOUNT_ID" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"dry_run": true}'Response
{
"data": {
"dry_run": true,
"would_do": "Disconnect LinkedIn account \"Anna Clarke\" from this workspace.",
"account": {
"id": "e4b8c2d1-7a3f-4b9e-8c1d-5f2a6e0b9c34",
"name": "Anna Clarke",
"status": "ok"
},
"used_by": {
"campaigns": [
{
"id": "c3a7e2f4-1b5d-4e8a-9c0f-6d2b8a4e7f15",
"name": "UK SaaS founders — Q4",
"status": "active"
}
],
"sdr_campaigns": [],
"sdr_searches": []
},
"warning": "Any campaign or SDR search sending from this account stops immediately. Sending capacity drops by roughly 70–80 invites/day per account removed.",
"note": "Re-send with { \"confirm\": true } to proceed."
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | confirmation_required | Neither dry_run nor confirm was sent (or both) |
| 404 | not_found | LinkedIn account does not exist or belongs to another workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: preview_disconnect_linkedin_account, disconnect_linkedin_account
POST/v1/linkedin/accounts/:id/reconnectReconnect a LinkedIn account
#post-v1-linkedin-accounts-id-reconnect
Starts re-authentication of an account whose session died: returns a hosted link a human must open. Reconnecting creates a NEW provider account upstream, so per-account history can look empty afterwards and running campaigns may need re-pointing. Destructive protocol. Fixed (2026-10-26): the account must be one of your workspace accounts, a foreign or unknown id answers 404 also for dry_run (it used to preview any id); the preview names the account and the campaigns / SDR searches using it.
Protected: send {"dry_run": true} to preview, then {"confirm": true} to execute (protocol).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | LinkedIn account id from GET /v1/linkedin/accounts |
dry_run | body | boolean | true = preview only: nothing is changed (HTTP 200) | |
confirm | body | boolean | true = execute. Send exactly one of dry_run / confirm |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/linkedin/accounts/$ACCOUNT_ID/reconnect" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"dry_run": true}'Response
{
"data": {
"dry_run": true,
"would_do": "Start re-authentication for LinkedIn account \"Anna Clarke\".",
"account": {
"id": "e4b8c2d1-7a3f-4b9e-8c1d-5f2a6e0b9c34",
"name": "Anna Clarke",
"status": "credentials"
},
"used_by": {
"campaigns": [
{
"id": "c3a7e2f4-1b5d-4e8a-9c0f-6d2b8a4e7f15",
"name": "UK SaaS founders — Q4",
"status": "active"
}
],
"sdr_campaigns": [],
"sdr_searches": []
},
"warning": "Reconnecting creates a NEW account id upstream…",
"note": "Re-send with { \"confirm\": true } to proceed. A human still has to finish the hosted flow in a browser."
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | confirmation_required | Neither dry_run nor confirm was sent (or both) |
| 404 | not_found | LinkedIn account does not exist or belongs to another workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: preview_reconnect_linkedin_account, reconnect_linkedin_account
POST/v1/linkedin/outreach/:outreachId/takeoverTake over a LinkedIn contact
#post-v1-linkedin-outreach-outreachid-takeover
Stops the AI from messaging this contact and cancels pending auto-replies. Nothing is sent.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
outreachId | path | uuid | yes | Outreach id from the LinkedIn queue or conversations. |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/linkedin/outreach/$OUTREACH_ID/takeover" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"id": "5f1c2a9e-8b7d-4c3e-9a61-2d4f7b0c1e83",
"campaign_id": "c3a7e2f4-1b5d-4e8a-9c0f-6d2b8a4e7f15",
"contact_name": "James Whitfield",
"status": "replied",
"human_takeover": true
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | Outreach not found for the key user. |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
Mailboxes & senders 5
GET/v1/email/mailboxesOwn mailboxes
The workspace's own connected mailboxes with live status (connecting, warming, ready, error, disconnected) and warm-up reputation. Capacity ≈ 50 emails/day per ready box. Used by classic campaigns and SDR in client_owned mode.
Parameters
No parameters.
Request
curl -s "https://api.vendo-ai.com/v1/email/mailboxes" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": [
{
"id": "0f3a6c9d-2e5b-4d8a-b1c4-7e9f2a5d8b16",
"email": "anna@brightlane-outreach.com",
"from_name": "Anna Clarke",
"provider": "google",
"status": "warming",
"warmup_reputation": 72,
"last_error": null,
"connected_by_user_id": "5f1c2a9e-8b7d-4c3e-9a61-2d4f7b0c1e83",
"created_at": "2026-10-20T09:00:00.000Z",
"updated_at": "2026-10-26T09:11:00.000Z"
}
],
"summary": {
"total": 1,
"ready": 0,
"emails_per_day_ready": 0
}
}Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
POST/v1/email/mailboxesConnect an own mailbox
Connects the user's own mailbox with SMTP/IMAP and an app password. The mailbox is registered with the sending operator WITH AUTOMATIC WARM-UP: the real mailbox starts sending and receiving warm-up mail immediately. The password is passed through, never stored by Vendo and never returned; audit logs redact it. Status error means the SMTP/IMAP handshake failed.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
email | body | string | yes | |
from_name | body | string | ||
provider | body | string | Default other. one of: google, microsoft, other | |
smtp_host | body | string | yes | Bare hostname, e.g. smtp.gmail.com. |
smtp_port | body | integer | yes | e.g. 465 or 587. |
imap_host | body | string | yes | e.g. imap.gmail.com. |
imap_port | body | integer | yes | e.g. 993. |
password | body | string | yes | App password. |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/email/mailboxes" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"email": "anna@brightlane-outreach.com",
"from_name": "Anna Clarke",
"provider": "google",
"smtp_host": "smtp.gmail.com",
"smtp_port": 465,
"imap_host": "imap.gmail.com",
"imap_port": 993,
"password": "abcd efgh ijkl mnop"
}'Response
{
"data": {
"id": "0f3a6c9d-2e5b-4d8a-b1c4-7e9f2a5d8b16",
"email": "anna@brightlane-outreach.com",
"from_name": "Anna Clarke",
"provider": "google",
"status": "warming",
"warmup_reputation": 0,
"last_error": null,
"connected_by_user_id": "5f1c2a9e-8b7d-4c3e-9a61-2d4f7b0c1e83",
"created_at": "2026-10-20T09:00:00.000Z",
"updated_at": "2026-10-26T09:11:00.000Z"
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | A parameter has the wrong type, range or enum value. |
| 400 | invalid_request | Mailbox already connected in this workspace, or SMTP/IMAP data missing. |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
DELETE/v1/email/mailboxes/:mailboxIdDisconnect a mailbox
#delete-v1-email-mailboxes-mailboxid
Stops using the mailbox as a sender (soft disconnect). dry_run lists client_owned campaigns that would be affected and how many usable mailboxes remain; with none left those campaigns cannot send or launch.
Protected: send {"dry_run": true} to preview, then {"confirm": true} to execute (protocol).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
mailboxId | path | uuid | yes | |
dry_run | body | boolean | true = preview only, nothing happens. | |
confirm | body | boolean | true = execute. Exactly one of dry_run / confirm is required. |
Request
curl -s -X DELETE "https://api.vendo-ai.com/v1/email/mailboxes/$MAILBOX_ID" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"dry_run": true}'Response
{
"data": {
"dry_run": true,
"would_do": "Disconnect mailbox anna@brightlane-outreach.com. It stops being used as a sender immediately.",
"mailbox": {
"id": "0f3a6c9d-2e5b-4d8a-b1c4-7e9f2a5d8b16",
"email": "anna@brightlane-outreach.com",
"from_name": "Anna Clarke",
"provider": "google",
"status": "warming",
"warmup_reputation": 72,
"last_error": null,
"connected_by_user_id": "5f1c2a9e-8b7d-4c3e-9a61-2d4f7b0c1e83",
"created_at": "2026-10-20T09:00:00.000Z",
"updated_at": "2026-10-26T09:11:00.000Z"
},
"remaining_usable_mailboxes": 0,
"client_owned_campaigns": [
{
"id": "c3a7e2f4-1b5d-4e8a-9c0f-6d2b8a4e7f15",
"name": "UK SaaS founders — Q4",
"status": "active"
}
],
"warning": "No usable own mailbox would remain: client_owned campaigns cannot send and cannot be launched."
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | confirmation_required | Neither dry_run nor confirm was sent. |
| 404 | not_found | Mailbox not in the workspace. |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: preview_disconnect_client_mailbox, disconnect_client_mailbox
POST/v1/email/mailboxes/:mailboxId/reconnectReconnect a mailbox
#post-v1-email-mailboxes-mailboxid-reconnect
Retries the SMTP/IMAP connection of a mailbox (status error) with the operator and refreshes its status.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
mailboxId | path | uuid | yes |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/email/mailboxes/$MAILBOX_ID/reconnect" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"id": "0f3a6c9d-2e5b-4d8a-b1c4-7e9f2a5d8b16",
"email": "anna@brightlane-outreach.com",
"from_name": "Anna Clarke",
"provider": "google",
"status": "ready",
"warmup_reputation": 88,
"last_error": null,
"connected_by_user_id": "5f1c2a9e-8b7d-4c3e-9a61-2d4f7b0c1e83",
"created_at": "2026-10-20T09:00:00.000Z",
"updated_at": "2026-10-26T09:11:00.000Z"
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | Mailbox not in the workspace. |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
GET/v1/email/sendersSenders and capacity
Sending addresses available to the workspace (Vendo managed pool + own mailboxes) with warm-up level, daily limit and status, plus capacity. Managed senders need a paid plan to launch (summary.paid_plan). Ids go into settings.senderEmailIds. provider_unavailable=true means the provider list could not be loaded (no demo data is returned).
Parameters
No parameters.
Request
curl -s "https://api.vendo-ai.com/v1/email/senders" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"senders": [
{
"id": "4419021",
"email": "alex@vendo-mail.io",
"domain": "vendo-mail.io",
"warmupLevel": 97,
"dailyLimit": 40,
"status": "ready"
}
],
"summary": {
"total": 1,
"ready": 1,
"emails_per_day_ready": 40,
"managed_requires_paid_plan": true,
"paid_plan": true,
"provider_unavailable": false
}
}
}Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: list_sender_emails
Phone campaigns 3
POST/v1/campaigns/:id/phone/activateActivate AI phone calls
#post-v1-campaigns-id-phone-activate
Schedules REAL outbound AI calls to every audience contact with a phone number, using numbers the account already owns (numbers cannot be bought through the API). Saves the phone settings on the campaign. dry_run returns numbers, objective, limits and estimated credits; credits are charged per call minute.
Credits: Per call minute (number rate, ~15/min) · Protected: send {"dry_run": true} to preview, then {"confirm": true} to execute (protocol).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | Campaign id. |
phone_number_ids | body | uuid[] | yes | Existing numbers of the account. |
call_objective | body | string | Default inform. one of: sell, inform, book_meeting, collect_info, qualify_lead | |
talking_points | body | string | ||
max_calls_per_day | body | integer | Default 50. | |
max_call_duration_seconds | body | integer | Default 300. | |
working_hours_start | body | integer | Default: campaign working hours. | |
working_hours_end | body | integer | ||
timezone | body | string | ||
exclude_weekends | body | boolean | ||
dry_run | body | boolean | true = preview only, nothing happens. | |
confirm | body | boolean | true = execute. Exactly one of dry_run / confirm is required. |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/campaigns/$CAMPAIGN_ID/phone/activate" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"phone_number_ids": [
"5f1c2a9e-8b7d-4c3e-9a61-2d4f7b0c1e83"
],
"call_objective": "book_meeting",
"dry_run": true
}'Response
{
"data": {
"dry_run": true,
"would_do": "Schedule REAL outbound AI phone calls to every contact with a phone number in \"UK SaaS founders — Q4\" (up to 50/day, 9:00–18:00 Europe/London, weekdays only).",
"numbers": [
{
"id": "5f1c2a9e-8b7d-4c3e-9a61-2d4f7b0c1e83",
"phone_number": "+14155550123",
"country": "US"
}
],
"objective": "book_meeting",
"max_calls_per_day": 50,
"max_call_duration_seconds": 300,
"audience_contacts": 412,
"estimated": {
"credits_per_minute": 15,
"max_credits_per_call": 75,
"max_credits_per_day": 3750
}
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | confirmation_required | Neither dry_run nor confirm was sent. |
| 400 | invalid_request | A parameter has the wrong type, range or enum value. |
| 404 | not_found | Campaign or phone number not owned. |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: preview_activate_campaign_phone, activate_campaign_phone
GET/v1/campaigns/:id/phone/callsCalls of a campaign
#get-v1-campaigns-id-phone-calls
Calls with contact, status, outcome, duration, sentiment, summary and recording URL.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | Campaign id. |
status | query | string | one of: pending, scheduled, calling, completed, failed, skipped, no_answer, voicemail, busy | |
limit | query | integer | Page size, 1–200 (default 50). | |
offset | query | integer | Rows to skip (default 0). |
Request
curl -s "https://api.vendo-ai.com/v1/campaigns/$CAMPAIGN_ID/phone/calls" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"items": [
{
"id": "5f1c2a9e-8b7d-4c3e-9a61-2d4f7b0c1e83",
"contact_name": "Sara Kim",
"contact_phone": "+447700900123",
"company_name": "Northwind Labs",
"status": "completed",
"call_outcome": "meeting_booked",
"call_duration_seconds": 142,
"call_sentiment": "positive",
"summary": "Booked a demo for Thursday.",
"recording_url": "https://recordings.vendo-ai.com/c/91a2.mp3",
"call_started_at": "2026-10-26T10:01:00.000Z",
"call_ended_at": "2026-10-26T10:03:22.000Z",
"created_at": "2026-10-25T18:00:00.000Z"
}
],
"total": 120
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | A parameter has the wrong type, range or enum value. |
| 404 | not_found | Campaign does not exist or belongs to another workspace. |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
GET/v1/campaigns/:id/phone/statsPhone stats
#get-v1-campaigns-id-phone-stats
Call counters and durations for the campaign.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | Campaign id. |
Request
curl -s "https://api.vendo-ai.com/v1/campaigns/$CAMPAIGN_ID/phone/stats" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"total": 120,
"completed": 64,
"active": 1,
"pending": 30,
"no_answer": 18,
"voicemail": 5,
"failed": 2,
"interested": 9,
"meetings_booked": 4,
"not_interested": 21,
"avg_duration_seconds": 96,
"total_duration_seconds": 6144
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | Campaign does not exist or belongs to another workspace. |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
Monitor, AI lead discovery & live stats 8
GET/v1/campaigns/:id/activityActivity feed
Paged activity events of the campaign (polling replacement for the live stream).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | Campaign id. |
category | query | string | one of: discovery, outreach, engagement, system, billing | |
limit | query | integer | Page size, 1–200 (default 50). | |
offset | query | integer | Rows to skip (default 0). |
Request
curl -s "https://api.vendo-ai.com/v1/campaigns/$CAMPAIGN_ID/activity" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"events": [
{
"id": "5f1c2a9e-8b7d-4c3e-9a61-2d4f7b0c1e83",
"event_type": "email_sent",
"event_category": "outreach",
"severity": "info",
"title": "Email sent to James Whitfield",
"created_at": "2026-10-26T13:41:09.000Z"
}
],
"total": 1232
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | A parameter has the wrong type, range or enum value. |
| 404 | not_found | Campaign does not exist or belongs to another workspace. |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
GET/v1/campaigns/:id/discoveryAI lead discovery config
#get-v1-campaigns-id-discovery
Discovery settings of a campaign, or null when never configured.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | Campaign id. |
Request
curl -s "https://api.vendo-ai.com/v1/campaigns/$CAMPAIGN_ID/discovery" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"id": "5f1c2a9e-8b7d-4c3e-9a61-2d4f7b0c1e83",
"campaign_id": "c3a7e2f4-1b5d-4e8a-9c0f-6d2b8a4e7f15",
"enabled": true,
"search_source": "apollo",
"apollo_search_config": {
"person_titles": [
"Head of Growth",
"VP Marketing"
],
"organization_keywords": [
"saas"
],
"person_locations": [
"United Kingdom"
]
},
"daily_discovery_budget_credits": 50,
"max_leads_per_day": 25,
"max_total_discovered": 1000,
"search_interval_minutes": 60,
"require_email": true,
"credits_spent_today": 12,
"credits_spent_total": 340
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | Campaign does not exist or belongs to another workspace. |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
PATCH/v1/campaigns/:id/discoveryEnable / change AI lead discovery
#patch-v1-campaigns-id-discovery
Creates or updates automatic lead discovery (Apollo people search or LinkedIn signal search). Whenever the resulting config is enabled it runs daily, SPENDS CREDITS up to the daily budget and grows the campaign audience, so the confirm protocol applies; disabling needs no confirmation.
Credits: Up to daily_discovery_budget_credits per day · Protected: send {"dry_run": true} to preview, then {"confirm": true} to execute (protocol).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | Campaign id. |
enabled | body | boolean | ||
search_source | body | string | one of: apollo, signal | |
apollo_search_config | body | object | {person_titles[], organization_keywords[], person_locations[]} | |
signal_search_config | body | object | {queries[], linkedin_account_id} | |
daily_discovery_budget_credits | body | integer | Max credits per day. | |
max_leads_per_day | body | integer | ||
max_total_discovered | body | integer | ||
search_interval_minutes | body | integer | ≥5 | |
require_email | body | boolean | ||
dry_run | body | boolean | true = preview only, nothing happens. | |
confirm | body | boolean | true = execute. Exactly one of dry_run / confirm is required. |
Request
curl -s -X PATCH "https://api.vendo-ai.com/v1/campaigns/$CAMPAIGN_ID/discovery" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"enabled": true,
"daily_discovery_budget_credits": 50,
"max_leads_per_day": 25,
"apollo_search_config": {
"person_titles": [
"Head of Growth"
],
"organization_keywords": [
"saas"
],
"person_locations": [
"United Kingdom"
]
},
"dry_run": true
}'Response
{
"data": {
"dry_run": true,
"would_do": "Enable AI lead discovery for campaign \"UK SaaS founders — Q4\" — it runs automatically and SPENDS CREDITS every day up to the daily budget, adding new leads to the campaign audience.",
"enabled": true,
"source": "apollo",
"daily_budget_credits": 50,
"max_leads_per_day": 25,
"max_total_discovered": null
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | confirmation_required | Neither dry_run nor confirm was sent. |
| 400 | invalid_request | A parameter has the wrong type, range or enum value. |
| 404 | not_found | Campaign does not exist or belongs to another workspace. |
| 404 | not_found | signal_search_config.linkedin_account_id not in the workspace. |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: preview_update_campaign_discovery, update_campaign_discovery
GET/v1/campaigns/:id/discovery/leadsDiscovered leads
#get-v1-campaigns-id-discovery-leads
Leads found by AI discovery with their processing status.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | Campaign id. |
status | query | string | one of: discovered, enriching, enriched, added_to_audience, rejected, duplicate, enrichment_failed | |
limit | query | integer | Page size, 1–200 (default 50). | |
offset | query | integer | Rows to skip (default 0). |
Request
curl -s "https://api.vendo-ai.com/v1/campaigns/$CAMPAIGN_ID/discovery/leads" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"leads": [
{
"id": "5f1c2a9e-8b7d-4c3e-9a61-2d4f7b0c1e83",
"source": "apollo",
"full_name": "Emma Lowe",
"title": "Head of Growth",
"company_name": "Tallyfin",
"email": "emma@tallyfin.co.uk",
"status": "added_to_audience",
"created_at": "2026-10-26T07:12:00.000Z"
}
],
"total": 410
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | A parameter has the wrong type, range or enum value. |
| 404 | not_found | Campaign does not exist or belongs to another workspace. |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
GET/v1/campaigns/:id/email-statsEmail funnel and delivery
#get-v1-campaigns-id-email-stats
Email funnel of a campaign including the Delivery block: delivered vs not delivered (bounced, rejected as spam, unsubscribed) and the sender inbox-placement test, plus unique opened/clicked/replied recipients. Providers do not report the real inbox vs spam folder of recipients.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | Campaign id. |
Request
curl -s "https://api.vendo-ai.com/v1/campaigns/$CAMPAIGN_ID/email-stats" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"sent": 1260,
"delivered": 1251,
"opened": 512,
"clicked": 41,
"replied": 37,
"bounced": 9,
"repliedReal": 29,
"repliedAutoReply": 6,
"repliedBounce": 2,
"delivery": {
"sent": 1260,
"delivered": 1251,
"bounced": 9,
"rejected_spam": 1,
"unsubscribed": 3,
"inbox_placement": {
"tested_at": "2026-10-24T08:00:00.000Z",
"inbox_pct": 92,
"spam_pct": 8
}
},
"unique": {
"opened": 388,
"clicked": 33,
"replied": 31
}
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | Campaign does not exist or belongs to another workspace. |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
GET/v1/campaigns/:id/live-statusEmail progress
#get-v1-campaigns-id-live-status
Email channel progress for the campaign page: audience size, processed contacts, percent complete, event counts, queue depth and the provider's status and sends today.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | Campaign id. |
Request
curl -s "https://api.vendo-ai.com/v1/campaigns/$CAMPAIGN_ID/live-status" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"totalInAudience": 412,
"totalProcessed": 318,
"percentComplete": 77,
"eventCounts": {
"sent": 318,
"skipped": 6,
"bounced": 4,
"opened": 141,
"clicked": 12,
"replied": 9
},
"queueDepth": 44,
"queueInFlight": 3,
"smartleadStatus": "ACTIVE",
"smartleadSentToday": 61
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | Campaign does not exist or belongs to another workspace. |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
GET/v1/campaigns/:id/monitorCampaign monitor
Everything the Monitor page shows: discovery config and stats, audience growth by day, the last 100 activity events, event counts and credits (balance, spent today/total, daily budget).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | Campaign id. |
Request
curl -s "https://api.vendo-ai.com/v1/campaigns/$CAMPAIGN_ID/monitor" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Show the full response (62 lines)
{
"data": {
"discoveryConfig": {
"id": "5f1c2a9e-8b7d-4c3e-9a61-2d4f7b0c1e83",
"campaign_id": "c3a7e2f4-1b5d-4e8a-9c0f-6d2b8a4e7f15",
"enabled": true,
"search_source": "apollo",
"apollo_search_config": {
"person_titles": [
"Head of Growth",
"VP Marketing"
],
"organization_keywords": [
"saas"
],
"person_locations": [
"United Kingdom"
]
},
"daily_discovery_budget_credits": 50,
"max_leads_per_day": 25,
"max_total_discovered": 1000,
"search_interval_minutes": 60,
"require_email": true,
"credits_spent_today": 12,
"credits_spent_total": 340
},
"discoveryStats": {
"discovered": 410,
"enriched": 352,
"added_to_audience": 301
},
"audienceGrowth": [
{
"date": "2026-10-25",
"total": 380,
"newLeads": 22
}
],
"recentActivity": [
{
"id": "5f1c2a9e-8b7d-4c3e-9a61-2d4f7b0c1e83",
"event_type": "lead_added",
"event_category": "discovery",
"severity": "success",
"title": "3 new leads added",
"created_at": "2026-10-26T10:00:00.000Z"
}
],
"eventCounts": {
"discovery": 120,
"outreach": 900,
"engagement": 210
},
"financials": {
"creditsBalance": 1840,
"creditsSpentToday": 12,
"creditsSpentTotal": 340,
"dailyBudget": 50
}
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | Campaign does not exist or belongs to another workspace. |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: get_campaign_monitor
GET/v1/campaigns/:id/pulseCampaign pulse
Dashboard pulse: what the campaign is doing now, today vs daily limits, a 7–28-day email and LinkedIn series and the latest events. Database only, fast.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | Campaign id. |
days | query | integer | Series length 7–28 (default 14). | |
limit | query | integer | Recent events 8–60. |
Request
curl -s "https://api.vendo-ai.com/v1/campaigns/$CAMPAIGN_ID/pulse" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Show the full response (89 lines)
{
"data": {
"campaign_id": "c3a7e2f4-1b5d-4e8a-9c0f-6d2b8a4e7f15",
"status": "active",
"channels": [
"email",
"linkedin"
],
"email": {
"sent": 1260,
"opened": 512,
"clicked": 41,
"replied": 37,
"bounced": 9,
"queued": 44,
"failed": 2,
"today": 61,
"daily_limit": 80,
"daily": [
52,
80,
79,
0,
0,
74,
61
],
"daily_replies": [
2,
4,
3,
0,
0,
5,
1
],
"last_sent_at": "2026-10-26T13:41:09.000Z"
},
"linkedin": {
"queued": 188,
"invites": 214,
"connected": 63,
"messages": 58,
"replied": 11,
"today": 22,
"daily_limit": 40,
"daily": [
30,
38,
40,
0,
0,
35,
22
],
"daily_connected": [
6,
9,
8,
2,
1,
7,
4
],
"accounts": 2,
"last_invite_at": "2026-10-26T13:20:00.000Z",
"last_reply_at": "2026-10-26T11:02:00.000Z"
},
"now": {
"state": "sending",
"line": "Sending: 61 of 80 emails today",
"last_event_at": "2026-10-26T13:41:09.000Z"
},
"recent": [
{
"at": "2026-10-26T13:41:09.000Z",
"channel": "email",
"kind": "sent",
"who": "James Whitfield",
"company": "Brightlane",
"avatar_url": null
}
],
"days": [
"2026-10-20",
"2026-10-26"
]
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | A parameter has the wrong type, range or enum value. |
| 404 | not_found | Campaign does not exist or belongs to another workspace. |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: get_campaign_pulse
Content (social) accounts 3
GET/v1/content/accountsSocial publishing accounts
LinkedIn and Instagram accounts connected for publishing posts (Settings › Social). Accounts of the key's user.
Parameters
No parameters.
Request
curl -s "https://api.vendo-ai.com/v1/content/accounts" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": [
{
"id": "5f1c2a9e-8b7d-4c3e-9a61-2d4f7b0c1e83",
"provider": "LINKEDIN",
"status": "connected",
"display_name": "Anna Clarke",
"username": "anna-clarke",
"avatar_url": "https://media.licdn.com/a.jpg",
"profile_url": "https://www.linkedin.com/in/anna-clarke",
"created_at": "2026-10-10T08:00:00.000Z"
}
]
}Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
POST/v1/content/accounts/connectConnect a publishing account
#post-v1-content-accounts-connect
Returns a hosted URL that a human must open to authorise a LinkedIn or Instagram publishing account; nothing is connected until they finish.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
provider | body | string | yes | one of: LINKEDIN, INSTAGRAM |
redirect_path | body | string | App path to return to (default /settings?tab=social). |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/content/accounts/connect" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"provider": "INSTAGRAM"}'Response
{
"data": {
"url": "https://account.unipile.com/hosted/abc123"
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | A parameter has the wrong type, range or enum value. |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
DELETE/v1/content/accounts/:accountIdDisconnect a publishing account
#delete-v1-content-accounts-accountid
Removes the publishing account; it can no longer publish posts.
Protected: send {"dry_run": true} to preview, then {"confirm": true} to execute (protocol).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
accountId | path | uuid | yes | |
dry_run | body | boolean | true = preview only, nothing happens. | |
confirm | body | boolean | true = execute. Exactly one of dry_run / confirm is required. |
Request
curl -s -X DELETE "https://api.vendo-ai.com/v1/content/accounts/$ACCOUNT_ID" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"confirm": true}'Response
{
"data": {
"ok": true
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | confirmation_required | Neither dry_run nor confirm was sent. |
| 404 | not_found | Account not found. |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: preview_disconnect_content_account, disconnect_content_account
Audiences API 26
Search the 4M+ People database, save audiences, manage uploaded custom audiences with enrichment, and buy ready-made Audience Sets.
People search & saved audiences 4
GET/v1/audiencesList the audiences saved in this workspace
list the audiences this workspace has saved. Without this, POST /v1/audiences was a one-way door: you could create an audience over the API but never see it again (Round-3 audit). List the CAMPAIGN audiences (filter-based, attachable to campaigns) of this workspace. Uploaded / purchased / CRM-built lists ("My Audiences") are listed by list_custom_audiences. Read-only, free.
Parameters
No parameters.
Request
curl -s "https://api.vendo-ai.com/v1/audiences" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: list_audiences
POST/v1/audiencesSave audience from search results
save a new audience from filters. The audience can then be attached to a campaign. No outreach happens here. Save a campaign audience from the same filter set as search_audience (call that first to check the counts). Returns the audience id to pass to create_campaign. No outreach happens here, no credits.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
name | body | string | Audience name (auto-generated when omitted) | |
filters | body | object | yes |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/audiences" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"filters": {}}'Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: save_audience
POST/v1/audiences/searchSearch the People DB (preview)
Free preview of a People-DB search: aggregate stats and a small sample; saves nothing. Now forwards every include AND exclude filter the app uses (exclusions, language, freshness and company keywords were silently dropped before).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
filters… | body | various | Filters: countries/country/excludeCountries, cities/city/excludedCities, industries/excludedIndustries, positions/position/excludedPositions, companySizes/companySize/excludedCompanySizes (startup|small|medium|large|enterprise), tags, companyKeywords, language (e.g. "russian"), maxAgeDays, mustHaveEmail/Linkedin/Phone/BookingUrl, searchMode (keyword|exact|semantic), semanticQuery, exactKeyword | |
limit | body | integer | Sample size, max 100 |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/audiences/search" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"countries": [
"United Kingdom"
],
"positions": [
"CFO",
"Finance Director"
],
"excludedIndustries": [
"Banking"
],
"companySizes": [
"small",
"medium"
],
"mustHaveEmail": true,
"limit": 10
}'Response
{
"data": {
"stats": {
"totalCompanies": 1840,
"totalContacts": 2311,
"withEmail": 2004,
"withPhone": 610,
"withLinkedin": 2240,
"byIndustry": {
"Financial Services": 980
}
},
"companies": [
{
"id": "c2b7e9a4-5d1f-4c8e-a3b6-9f0e1d2c3b4a",
"name": "Amelia Hart",
"position": "CFO",
"company_name": "Ledgerly"
}
]
}
}Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: search_audience
GET/v1/audiences/:idRead one saved audience with its filters and contact count
one saved audience with its filters and counts. Declared AFTER the /sets routes so "sets" is never matched as an :id. Read one saved audience with its filters and contact count. Read-only, free.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string | yes |
Request
curl -s "https://api.vendo-ai.com/v1/audiences/$AUDIENCE_ID" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: get_audience
Custom audiences (My Audiences) 16
GET/v1/audiences/customList custom audiences
Lists the workspace's custom audiences ("My Audiences": uploaded files, purchased sets, CRM selections) with status, contact counts and enrichment progress. These are not campaign audiences (GET /v1/audiences). Without limit the whole list is returned.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
page | query | integer | Page (1-based), used with limit | |
limit | query | integer | 1..100; omit for all |
Request
curl -s "https://api.vendo-ai.com/v1/audiences/custom" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": [
{
"id": "3f9c2a1e-7b4d-4e2a-9c1f-2d8e5a6b7c90",
"user_id": "5e6f7a8b-9c0d-4e1f-8a2b-3c4d5e6f7a8b",
"workspace_id": "8a1d4c3b-2e5f-4a6b-9d7c-1e2f3a4b5c6d",
"name": "UK fintech CFOs",
"description": null,
"status": "ready",
"source_file_name": "uk-fintech-cfos.csv",
"total_contacts": 412,
"with_email": 388,
"with_phone": 97,
"with_linkedin": 405,
"processing_errors": null,
"tags": [],
"column_mapping": {
"0": "first_name",
"1": "last_name",
"2": "email",
"3": "company_name"
},
"enrichment_progress": 0,
"enrichment_total": 0,
"enrichment_done": 0,
"created_at": "2026-10-26T09:14:22.000Z",
"updated_at": "2026-10-26T09:14:22.000Z"
}
]
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | page/limit out of range |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
POST/v1/audiences/customCreate an empty custom audience
Creates an empty, named custom audience (status ready), the CRM "Save to new audience" action. Fill it with POST /v1/audiences/custom/{id}/contacts. No credits.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
name | body | string | yes | Audience name, 1..255 chars |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/audiences/custom" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name": "Webinar sign-ups Oct"}'Response
{
"data": {
"success": true,
"audience": {
"id": "3f9c2a1e-7b4d-4e2a-9c1f-2d8e5a6b7c90",
"user_id": "5e6f7a8b-9c0d-4e1f-8a2b-3c4d5e6f7a8b",
"workspace_id": "8a1d4c3b-2e5f-4a6b-9d7c-1e2f3a4b5c6d",
"name": "Webinar sign-ups Oct",
"description": null,
"status": "ready",
"source_file_name": null,
"total_contacts": 0,
"with_email": 0,
"with_phone": 0,
"with_linkedin": 0,
"processing_errors": null,
"tags": [],
"column_mapping": null,
"enrichment_progress": 0,
"enrichment_total": 0,
"enrichment_done": 0,
"created_at": "2026-10-26T09:14:22.000Z",
"updated_at": "2026-10-26T09:14:22.000Z"
}
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | name missing or too long |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
POST/v1/audiences/custom/previewPreview a contacts file (column mapping)
#post-v1-audiences-custom-preview
Step 1 of an upload: detects the columns of a CSV/XLSX/XLS/TSV/TXT file and suggests a mapping to contact fields, with sample rows. Creates nothing and spends nothing. Accepts multipart/form-data (field "file", ≤50 MB) or JSON (content_base64 + filename, or file_url).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
file | body | file (multipart) | The file, as multipart/form-data field "file". Alternatively send JSON with content_base64 + filename, or file_url | |
filename | body | string | JSON variant: file name with extension (.csv, .xlsx, .xls, .tsv, .txt); required with content_base64 | |
content_base64 | body | string | JSON variant: file content as base64 (request body ≤10 MB, i.e. ≈7 MB file) | |
file_url | body | string | JSON variant: public http(s) URL fetched server-side (SSRF-protected, ≤10 MB) |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/audiences/custom/preview" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"filename": "leads.csv",
"content_base64": "Zmlyc3RfbmFtZSxsYXN0X25hbWUsZW1haWwKQW1lbGlhLEhhcnQsYW1lbGlhQGxlZGdlcmx5LmNvLnVr"
}'Response
Show the full response (36 lines)
{
"data": {
"success": true,
"headers": [
"first_name",
"last_name",
"email"
],
"suggestedMapping": {
"0": "first_name",
"1": "last_name",
"2": "email"
},
"sampleRows": [
[
"Amelia",
"Hart",
"amelia@ledgerly.co.uk"
]
],
"totalRows": 1,
"delimiter": ",",
"fieldOptions": [
{
"value": "first_name",
"label": "First name",
"core": true
},
{
"value": "email",
"label": "Email",
"core": true
}
]
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | No file, unsupported extension or empty base64 |
| 400 | forbidden_url | file_url points to a private network address |
| 413 | payload_too_large | Body larger than the limit |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
POST/v1/audiences/custom/uploadCreate a custom audience from a file
#post-v1-audiences-custom-upload
Creates a custom audience from a CSV/XLSX/XLS/TSV/TXT file; parsing runs in the background, so poll GET /v1/audiences/custom/{id} until status is "ready" (or "error"). Optional column_mapping (from the preview) overrides auto-detection. No credits, enrichment is a separate paid step.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
file | body | file (multipart) | The file, as multipart/form-data field "file". Alternatively send JSON with content_base64 + filename, or file_url | |
filename | body | string | JSON variant: file name with extension (.csv, .xlsx, .xls, .tsv, .txt); required with content_base64 | |
content_base64 | body | string | JSON variant: file content as base64 (request body ≤10 MB, i.e. ≈7 MB file) | |
file_url | body | string | JSON variant: public http(s) URL fetched server-side (SSRF-protected, ≤10 MB) | |
name | body | string | Audience name (default: file name) | |
column_mapping | body | object | { "<column index>": "<field>" }; JSON string in multipart |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/audiences/custom/upload" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"file_url": "https://example.com/exports/uk-fintech-cfos.csv",
"name": "UK fintech CFOs",
"column_mapping": {
"0": "first_name",
"1": "last_name",
"2": "email",
"3": "company_name"
}
}'Response
{
"data": {
"success": true,
"audience": {
"id": "3f9c2a1e-7b4d-4e2a-9c1f-2d8e5a6b7c90",
"name": "UK fintech CFOs",
"status": "processing",
"source_file_name": "uk-fintech-cfos.csv"
}
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | No file, unsupported type or malformed column_mapping |
| 400 | fetch_failed | file_url could not be downloaded |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
GET/v1/audiences/custom/:idGet a custom audience
One custom audience with live counters, enrichment progress, column mapping and the first 20 contacts. Use the contacts route to page through all of them.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | Custom audience id |
Request
curl -s "https://api.vendo-ai.com/v1/audiences/custom/$AUDIENCE_ID" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Show the full response (48 lines)
{
"data": {
"id": "3f9c2a1e-7b4d-4e2a-9c1f-2d8e5a6b7c90",
"user_id": "5e6f7a8b-9c0d-4e1f-8a2b-3c4d5e6f7a8b",
"workspace_id": "8a1d4c3b-2e5f-4a6b-9d7c-1e2f3a4b5c6d",
"name": "UK fintech CFOs",
"description": null,
"status": "ready",
"source_file_name": "uk-fintech-cfos.csv",
"total_contacts": 412,
"with_email": 388,
"with_phone": 97,
"with_linkedin": 405,
"processing_errors": null,
"tags": [],
"column_mapping": {
"0": "first_name",
"1": "last_name",
"2": "email",
"3": "company_name"
},
"enrichment_progress": 0,
"enrichment_total": 0,
"enrichment_done": 0,
"created_at": "2026-10-26T09:14:22.000Z",
"updated_at": "2026-10-26T09:14:22.000Z",
"contacts_preview": [
{
"id": "c2b7e9a4-5d1f-4c8e-a3b6-9f0e1d2c3b4a",
"audience_id": "3f9c2a1e-7b4d-4e2a-9c1f-2d8e5a6b7c90",
"first_name": "Amelia",
"last_name": "Hart",
"full_name": "Amelia Hart",
"email": "amelia.hart@ledgerly.co.uk",
"phone": null,
"linkedin_url": "https://www.linkedin.com/in/ameliahart",
"company_name": "Ledgerly",
"position": "CFO",
"country": "United Kingdom",
"city": "London",
"enriched": false,
"signal_context": null,
"created_at": "2026-10-26T09:14:22.000Z"
}
],
"contacts_note": "First 20 contacts only — page through GET /v1/audiences/custom/{id}/contacts for all of them."
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | Audience does not exist in this workspace |
| 400 | invalid_request | id is not a UUID |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: get_custom_audience
PATCH/v1/audiences/custom/:idRename a custom audience
Renames a custom audience. Reversible, no credits.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | Custom audience id |
name | body | string | yes | New name, 1..255 chars |
Request
curl -s -X PATCH "https://api.vendo-ai.com/v1/audiences/custom/$AUDIENCE_ID" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name": "UK fintech CFOs — Q4"}'Response
{
"data": {
"id": "3f9c2a1e-7b4d-4e2a-9c1f-2d8e5a6b7c90",
"user_id": "5e6f7a8b-9c0d-4e1f-8a2b-3c4d5e6f7a8b",
"workspace_id": "8a1d4c3b-2e5f-4a6b-9d7c-1e2f3a4b5c6d",
"name": "UK fintech CFOs — Q4",
"description": null,
"status": "ready",
"source_file_name": "uk-fintech-cfos.csv",
"total_contacts": 412,
"with_email": 388,
"with_phone": 97,
"with_linkedin": 405,
"processing_errors": null,
"tags": [],
"column_mapping": {
"0": "first_name",
"1": "last_name",
"2": "email",
"3": "company_name"
},
"enrichment_progress": 0,
"enrichment_total": 0,
"enrichment_done": 0,
"created_at": "2026-10-26T09:14:22.000Z",
"updated_at": "2026-10-26T09:14:22.000Z"
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | Audience does not exist in this workspace |
| 400 | invalid_request | name missing |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
DELETE/v1/audiences/custom/:idDelete a custom audience
#delete-v1-audiences-custom-id
Permanently deletes the audience, all its contacts and the source file. dry_run returns the name and contact count; confirm deletes. Campaign audiences already built from it are not removed.
Protected: send {"dry_run": true} to preview, then {"confirm": true} to execute (protocol).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | Custom audience id |
dry_run | body | boolean | true = preview only, nothing changes | |
confirm | body | boolean | true = execute. Exactly one of dry_run / confirm is required |
Request
curl -s -X DELETE "https://api.vendo-ai.com/v1/audiences/custom/$AUDIENCE_ID" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"dry_run": true}'Response
{
"data": {
"dry_run": true,
"would_do": "Permanently delete the audience \"UK fintech CFOs\" with all 412 contacts and its source file. Campaign audiences already created from it keep working until their campaigns finish. Cannot be undone.",
"estimated": {
"audience": {
"id": "3f9c2a1e-7b4d-4e2a-9c1f-2d8e5a6b7c90",
"name": "UK fintech CFOs",
"status": "ready",
"total_contacts": 412
}
}
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | confirmation_required | Neither dry_run nor confirm was sent |
| 400 | bad_request | Both dry_run and confirm were sent |
| 404 | not_found | Audience does not exist in this workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
GET/v1/audiences/custom/:id/contactsList contacts of a custom audience
#get-v1-audiences-custom-id-contacts
Paged contacts (max 100 per page). Contacts added by Vendo SDR carry signal_context explaining why they were picked (reasoning, relevance score, ICP group).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | Custom audience id |
page | query | integer | Default 1 | |
limit | query | integer | 1..100, default 50 |
Request
curl -s "https://api.vendo-ai.com/v1/audiences/custom/$AUDIENCE_ID/contacts" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"contacts": [
{
"id": "c2b7e9a4-5d1f-4c8e-a3b6-9f0e1d2c3b4a",
"audience_id": "3f9c2a1e-7b4d-4e2a-9c1f-2d8e5a6b7c90",
"first_name": "Amelia",
"last_name": "Hart",
"full_name": "Amelia Hart",
"email": "amelia.hart@ledgerly.co.uk",
"phone": null,
"linkedin_url": "https://www.linkedin.com/in/ameliahart",
"company_name": "Ledgerly",
"position": "CFO",
"country": "United Kingdom",
"city": "London",
"enriched": false,
"signal_context": null,
"created_at": "2026-10-26T09:14:22.000Z"
}
],
"total": 412,
"page": 1,
"totalPages": 9
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | Audience does not exist in this workspace |
| 400 | invalid_request | page/limit out of range |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
POST/v1/audiences/custom/:id/contactsAdd contacts to a custom audience
#post-v1-audiences-custom-id-contacts
Appends up to 5000 contacts per call. Deduplicated inside the audience by email, LinkedIn URL or phone; duplicates and contacts with no way to reach them are skipped (not overwritten). No credits.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | Custom audience id |
contacts | body | array | yes | [{ first_name, last_name, email, phone, linkedin_url, company_name, position }], 1..5000 |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/audiences/custom/$AUDIENCE_ID/contacts" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"contacts": [
{
"first_name": "Tom",
"last_name": "Reyes",
"email": "tom@brightpay.io",
"company_name": "BrightPay",
"position": "Head of Finance"
}
]
}'Response
{
"data": {
"success": true,
"added": 1,
"skipped": 0,
"total_contacts": 413
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | Audience does not exist in this workspace |
| 400 | invalid_request | contacts empty or more than 5000 |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
PATCH/v1/audiences/custom/:id/contacts/:contactIdEdit a contact
#patch-v1-audiences-custom-id-contacts-contactid
Updates the given fields of one contact (first_name, last_name, full_name, email, phone, linkedin_url, company_name, position, country, city, website, whatsapp, telegram, facebook_url, instagram_url). Other fields are ignored. The response returns the updated contact row without signal_context (read it with GET …/contacts).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | Custom audience id |
contactId | path | uuid | yes | Contact id |
email | body | string | Any editable field, see description |
Request
curl -s -X PATCH "https://api.vendo-ai.com/v1/audiences/custom/$AUDIENCE_ID/contacts/$CONTACT_ID" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"email": "amelia.hart@ledgerly.com", "position": "Chief Financial Officer"}'Response
{
"data": {
"id": "c2b7e9a4-5d1f-4c8e-a3b6-9f0e1d2c3b4a",
"audience_id": "3f9c2a1e-7b4d-4e2a-9c1f-2d8e5a6b7c90",
"first_name": "Amelia",
"last_name": "Hart",
"full_name": "Amelia Hart",
"email": "amelia.hart@ledgerly.com",
"phone": null,
"linkedin_url": "https://www.linkedin.com/in/ameliahart",
"company_name": "Ledgerly",
"position": "Chief Financial Officer",
"country": "United Kingdom",
"city": "London",
"enriched": false,
"created_at": "2026-10-26T09:14:22.000Z"
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | Audience or contact does not exist in this workspace |
| 400 | invalid_request | No editable field given |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
DELETE/v1/audiences/custom/:id/contacts/:contactIdRemove a contact
#delete-v1-audiences-custom-id-contacts-contactid
Permanently removes one contact from the audience. The optional reason (UI: Not relevant / Wrong contact / Already a customer / Bad data / Other) is logged to improve automatic selection.
Protected: send {"dry_run": true} to preview, then {"confirm": true} to execute (protocol).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | Custom audience id |
contactId | path | uuid | yes | Contact id |
reason | body | string | Why it is removed (≤200 chars) | |
dry_run | body | boolean | true = preview only, nothing changes | |
confirm | body | boolean | true = execute. Exactly one of dry_run / confirm is required |
Request
curl -s -X DELETE "https://api.vendo-ai.com/v1/audiences/custom/$AUDIENCE_ID/contacts/$CONTACT_ID" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"confirm": true, "reason": "Already a customer"}'Response
{
"data": {
"success": true
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | confirmation_required | Neither dry_run nor confirm was sent |
| 400 | bad_request | Both dry_run and confirm were sent |
| 404 | not_found | Audience or contact does not exist in this workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: preview_delete_custom_audience_contact, delete_custom_audience_contact
POST/v1/audiences/custom/:id/enrichEnrich a custom audience
#post-v1-audiences-custom-id-enrich
Starts background enrichment (email, phone, LinkedIn, WhatsApp lookups) of the not-yet-enriched contacts. dry_run returns how many contacts would be processed, the maximum cost, estimated minutes and the balance; confirm starts it. Credits are charged per contact actually enriched while it runs; it needs at least 1 credit to start. Poll GET …/enrichment.
Credits: 1 credit per successfully enriched contact (max = to_enrich) · Protected: send {"dry_run": true} to preview, then {"confirm": true} to execute (protocol).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | Custom audience id |
dry_run | body | boolean | true = preview only, nothing changes | |
confirm | body | boolean | true = execute. Exactly one of dry_run / confirm is required |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/audiences/custom/$AUDIENCE_ID/enrich" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"dry_run": true}'Response
{
"data": {
"dry_run": true,
"would_do": "Enrich 380 not-yet-enriched contacts of \"UK fintech CFOs\" (email, phone, LinkedIn, WhatsApp lookups). Runs in the background (~26 min); poll GET /v1/audiences/custom/3f9c…/enrichment. Charged 1 credit per contact that is actually enriched — at most the estimate. Needs at least 1 credit to start; stops when credits run out.",
"estimated": {
"to_enrich": 380,
"max_credits": 380,
"estimated_minutes": 26,
"balance": 2140,
"can_start": true,
"current_status": "ready"
}
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | confirmation_required | Neither dry_run nor confirm was sent |
| 400 | bad_request | Both dry_run and confirm were sent |
| 404 | not_found | Audience does not exist in this workspace |
| 402 | insufficient_credits | Balance below 1 credit |
| 409 | conflict | Already enriching, or nothing left to enrich |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
POST/v1/audiences/custom/:id/enrich/stopStop enrichment
#post-v1-audiences-custom-id-enrich-stop
Stops a running enrichment after the current contact. Contacts already enriched stay enriched (and charged).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | Custom audience id |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/audiences/custom/$AUDIENCE_ID/enrich/stop" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"success": true,
"message": "Stop requested — will halt after current contact"
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | Audience does not exist in this workspace |
| 409 | conflict | Enrichment is not running |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
GET/v1/audiences/custom/:id/enrichmentEnrichment progress
#get-v1-audiences-custom-id-enrichment
Polling replacement for the app's live progress stream: status, totals, done count and percentage. done=true once the audience is no longer enriching/processing.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | Custom audience id |
Request
curl -s "https://api.vendo-ai.com/v1/audiences/custom/$AUDIENCE_ID/enrichment" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"id": "3f9c2a1e-7b4d-4e2a-9c1f-2d8e5a6b7c90",
"name": "UK fintech CFOs",
"status": "enriching",
"enrichment_total": 380,
"enrichment_done": 112,
"enrichment_progress": 29,
"total_contacts": 412,
"with_email": 388,
"with_phone": 131,
"with_linkedin": 405,
"done": false
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | Audience does not exist in this workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
GET/v1/audiences/custom/:id/export.csvExport a custom audience as CSV
#get-v1-audiences-custom-id-export-csv
Downloads every contact as CSV (Content-Disposition filename, X-Total-Rows header). With ?format=json the CSV is wrapped as { filename, total_rows, csv } (used by MCP).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | Custom audience id |
format | query | string | "json" to get a JSON envelope one of: json |
Request
curl -s "https://api.vendo-ai.com/v1/audiences/custom/$AUDIENCE_ID/export.csv" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"filename": "uk-fintech-cfos.csv",
"total_rows": 412,
"csv": "first_name,last_name,email,phone,linkedin_url,company_name,position\nAmelia,Hart,amelia.hart@ledgerly.co.uk,,https://www.linkedin.com/in/ameliahart,Ledgerly,CFO\n"
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | Audience does not exist in this workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
POST/v1/audiences/custom/:id/to-campaign-audienceUse a custom audience in a campaign
#post-v1-audiences-custom-id-to-campaign-audience
The "Launch campaign" button of My Audiences: wraps a READY custom audience as a campaign audience and returns its id for POST /v1/campaigns (audienceId). Contacts stay in the custom audience; nothing is sent and no credits are spent.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | Custom audience id |
name | body | string | Campaign audience name |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/audiences/custom/$AUDIENCE_ID/to-campaign-audience" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name": "UK fintech CFOs — Nov campaign"}'Response
{
"data": {
"id": "c2b7e9a4-5d1f-4c8e-a3b6-9f0e1d2c3b4a",
"name": "UK fintech CFOs — Nov campaign",
"status": "ready",
"stats": {
"totalContacts": 412,
"withEmail": 388,
"withPhone": 131,
"withLinkedin": 405
}
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | Audience does not exist in this workspace |
| 409 | conflict | Audience is still processing/enriching or in error |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
Audience Sets marketplace 6
GET/v1/audiences/setsBrowse the marketplace catalog and its categories. Free.
Browse pre-built, ready-to-use audiences available for purchase, plus their categories. Browsing is free; buying costs credits. Read-only.
Parameters
No parameters.
Request
curl -s "https://api.vendo-ai.com/v1/audiences/sets" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: list_audience_sets
POST/v1/audiences/sets/:id/buyBuy an audience set
#post-v1-audiences-sets-id-buy
Purchases a marketplace set and snapshots its contacts into My Audiences; credits are deducted immediately. dry_run now returns the set, contact count, price per contact, estimated credits, balance and can_afford.
Credits: price_per_contact × contacts (usually 2 per contact) · Protected: send {"dry_run": true} to preview, then {"confirm": true} to execute (protocol).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | Set id |
dry_run | body | boolean | true = preview only, nothing changes | |
confirm | body | boolean | true = execute. Exactly one of dry_run / confirm is required |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/audiences/sets/$SET_ID/buy" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"dry_run": true}'Response
{
"data": {
"dry_run": true,
"would_do": "Buy \"UK fintech founders\" and snapshot about 640 contacts into My Audiences (GET /v1/audiences/custom).",
"estimated": {
"set": {
"id": "3f9c2a1e-7b4d-4e2a-9c1f-2d8e5a6b7c90",
"slug": "uk-fintech-founders",
"title": "UK fintech founders",
"source": "people",
"est_count": 640,
"price_per_contact": 2,
"purchased": false
},
"contact_count": 640,
"price_per_contact": 2,
"estimated_credits": 1280,
"balance": 2140,
"can_afford": true
},
"cost": "2 credits per contact; the exact count is fixed at purchase time (est. 1280 credits).",
"note": "Re-send with { \"confirm\": true } to execute. Credits are deducted immediately."
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | confirmation_required | Neither dry_run nor confirm was sent |
| 400 | bad_request | Both dry_run and confirm were sent |
| 404 | not_found | Unknown set id |
| 400 | invalid_request | Insufficient credits or set unavailable |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
POST/v1/audiences/sets/:id/launchPrepare a campaign audience from a set
#post-v1-audiences-sets-id-launch
Creates a campaign-ready audience from a set and returns its audienceId, it does NOT contact anyone (the old description wrongly said it started outreach). Signals sets buy a fresh snapshot first (price per contact, also added to My Audiences); people sets are free. Next: POST /v1/campaigns with audienceId, then preview and launch. Scope is now audiences:write only.
Credits: signals sets: price_per_contact × contacts; people sets: free · Protected: send {"dry_run": true} to preview, then {"confirm": true} to execute (protocol).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | Set id |
dry_run | body | boolean | true = preview only, nothing changes | |
confirm | body | boolean | true = execute. Exactly one of dry_run / confirm is required |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/audiences/sets/$SET_ID/launch" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"confirm": true}'Response
{
"data": {
"audienceId": "c2b7e9a4-5d1f-4c8e-a3b6-9f0e1d2c3b4a",
"name": "UK fintech founders",
"contactCount": 612
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | confirmation_required | Neither dry_run nor confirm was sent |
| 400 | bad_request | Both dry_run and confirm were sent |
| 404 | not_found | Unknown set id |
| 400 | invalid_request | Insufficient credits or set cannot be launched |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
GET/v1/audiences/sets/:slugDetails of one set: size, description, freshness. slug is human-readable (e.g. uk-fintech-founders), not a UUID.
Details of one marketplace set by slug: size, description, freshness. Use the contact count here to work out the purchase cost (2 credits per contact). Read-only, free.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
slug | path | string | yes | Human-readable slug, not a UUID |
Request
curl -s "https://api.vendo-ai.com/v1/audiences/sets/$SET_SLUG" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: get_audience_set
GET/v1/audiences/sets/:slug/contactsPaginated contact preview. Accepts offset and limit. Contact details stay masked until purchase.
#get-v1-audiences-sets-slug-contacts
Paginated preview of the contacts inside a marketplace set. Contact details stay masked until the set is purchased. Read-only, free.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
slug | path | string | yes | |
offset | query | number | ||
limit | query | number |
Request
curl -s "https://api.vendo-ai.com/v1/audiences/sets/$SET_SLUG/contacts" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
GET/v1/audiences/sets/:slug/contacts/:contactIdOne contact of an audience set
#get-v1-audiences-sets-slug-contacts-contactid
Full card of one contact in a marketplace set: person, company, buying signal and channels. Contact details stay masked until the set is purchased. Free. Response shape (docs fixed 2026-10-26): id, locked, source, person {full_name, title, …}, company, signal and channels {email (masked until unlocked), has_email, linkedin_url (null until unlocked), phone (masked), …}.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
slug | path | string | yes | Set slug |
contactId | path | string | yes | Contact id from GET /v1/audiences/sets/{slug}/contacts |
Request
curl -s "https://api.vendo-ai.com/v1/audiences/sets/$SET_SLUG/contacts/$CONTACT_ID" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Show the full response (31 lines)
{
"data": {
"id": "7d2a9f4c-1b3e-4c8a-9e6d-0f5b2a8c4e19",
"locked": true,
"source": "linkedin_jobs",
"person": {
"full_name": "Sarah Mitchell",
"title": "Practice Manager",
"seniority": "manager",
"location": "Leeds, United Kingdom"
},
"company": {
"name": "Northgate Dental",
"domain": "northgatedental.co.uk",
"industry": "Dental clinics",
"size": "11-50"
},
"signal": {
"type": "hiring",
"summary": "Hiring a dental receptionist",
"detected_at": "2026-10-24T08:12:00.000Z"
},
"channels": {
"email": "s•••@northgatedental.co.uk",
"has_email": true,
"linkedin_url": null,
"phone": "•••",
"has_phone": true
}
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | Unknown slug or contact not in the set |
| 400 | invalid_request | Malformed slug or contactId |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
CRM API 48
The built-in Vendo CRM: leads, pipelines (branches) and stages, documents, the communications stream, calendar, notifications and settings.
Leads 16
GET/v1/crm/leadsList pipeline leads. Query: stage, source, q, range (24h…1y|all), limit (≤200), offset
List leads in the CRM pipeline. Filters: branch ("general" or a branch id from crm_list_branches; omitted = all branches visible to the user), stage (stage id), owner (user id from crm_list_workspace_members), source, q (name/company/email search), range or from/to, limit/offset. Read-only. Great first call for "who replied to me?".
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
branch | query | string | ||
stage | query | string | ||
owner | query | string | Assigned user id | |
source | query | string | one of: linkedin_outreach_reply, linkedin_inbound, linkedin_manual, email_reply_real, widget_chat, widget_voice, widget_form, narrator_form, telegram_message, whatsapp_message, phone_call_interested, meeting_booked, crm_integration_inbound, manual | |
q | query | string | ||
range | query | string | Omit for all time one of: 24h, 7d, 30d, 90d, 180d, 1y | |
from | query | string | ISO date, created from | |
to | query | string | ISO date, created to | |
limit | query | integer | min 1; max 200 | |
offset | query | integer | min 0 |
Request
curl -s "https://api.vendo-ai.com/v1/crm/leads" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: crm_list_leads
POST/v1/crm/leadsCreate a lead
Adds a lead card (all fields optional, at least one identity field). Now verifies that assigned_user_id is a member of this workspace and that branch_id / branch_ids are pipelines of this workspace before creating.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
first_name | body | string | ||
last_name | body | string | ||
company_name | body | string | ||
title | body | string | ||
email | body | string | ||
linkedin_url | body | string | http(s) URL | |
phone | body | string | ||
avatar_url | body | string | ||
instagram_url | body | string | ||
facebook_url | body | string | ||
assigned_user_id | body | uuid | Owner, a workspace member | |
notes | body | string | ≤8000 | |
branch_ids | body | string[] | "general" and/or branch ids |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/crm/leads" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"first_name": "Tom",
"last_name": "Reyes",
"company_name": "BrightPay",
"email": "tom@brightpay.io",
"branch_ids": [
"general",
"8a1d4c3b-2e5f-4a6b-9d7c-1e2f3a4b5c6d"
]
}'Response
{
"data": {
"id": "c2b7e9a4-5d1f-4c8e-a3b6-9f0e1d2c3b4a",
"first_name": "Tom",
"last_name": "Reyes",
"company_name": "BrightPay",
"email": "tom@brightpay.io",
"source": "manual",
"stage_id": "3f9c2a1e-7b4d-4e2a-9c1f-2d8e5a6b7c90",
"assigned_user_id": "5e6f7a8b-9c0d-4e1f-8a2b-3c4d5e6f7a8b",
"created_at": "2026-10-26T09:14:22.000Z"
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | Empty card, invalid field, foreign assignee or unknown branch |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: crm_create_lead
POST/v1/crm/leads/enrichEnrich from LinkedIn URL → name, title, employer, avatar, location, and unverified pattern-guess email. Consumes 1 LinkedIn budget
Enrich from LinkedIn URL (same Unipile lookup the SDR hunter uses; consumes the workspace's shared daily profile-view budget). Fetch name / title / current employer / avatar / location for a linkedin.com/in/... URL (same lookup the SDR hunters use). May also suggest an email as an UNVERIFIED pattern guess (email_confidence=pattern_guess). Costs one profile view from the workspace's shared daily LinkedIn budget, do not loop over big lists.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
linkedin_url | body | string | yes |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/crm/leads/enrich" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"linkedin_url": "<linkedin_url>"}'Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
POST/v1/crm/leads/importImport leads
Imports up to 1000 already-parsed rows into the chosen pipelines (entry stage). Deduplicated by email/phone: an existing lead only gets blank fields filled (never overwritten) and is added to the pipelines. Returns a summary with skipped rows and reasons.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
rows | body | array | yes | [{ first_name, last_name, email, phone, company, title, linkedin, instagram, facebook }], 1..1000 |
branch_ids | body | string[] | "general" and/or branch ids (default General) |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/crm/leads/import" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"rows": [
{
"first_name": "Tom",
"last_name": "Reyes",
"email": "tom@brightpay.io",
"company": "BrightPay",
"title": "Head of Finance"
}
],
"branch_ids": [
"general"
]
}'Response
{
"data": {
"created": 1,
"updated": 0,
"skipped": 0,
"skippedRows": []
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | rows empty, more than 1000, or unknown branch |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: crm_import_leads
GET/v1/crm/leads/:idFull lead card: identity, origin, company enrichment, activity, comments
Full lead card: identity, source ("why is this person here"), company enrichment, activities, comments, assignments. Read-only.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string | yes |
Request
curl -s "https://api.vendo-ai.com/v1/crm/leads/$LEAD_ID" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: crm_get_lead
PATCH/v1/crm/leads/:idUpdate lead attributes
Patch lead attributes (name, company, title, email, LinkedIn, phone, notes, tags, deal value + currency). Stage and owner have their own tools (crm_move_lead, reassign_crm_lead). Reversible.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string | yes | |
first_name | body | string | ||
last_name | body | string | ||
company_name | body | string | ||
title | body | string | ||
email | body | string | ||
linkedin_url | body | string | Full http(s) URL | |
phone | body | string | ||
notes | body | string | ||
tags | body | string[] | Replaces the tag list | |
deal_value | body | number | ||
currency | body | string | ISO code, e.g. USD max length 3 |
Request
curl -s -X PATCH "https://api.vendo-ai.com/v1/crm/leads/$LEAD_ID" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: crm_update_lead
DELETE/v1/crm/leads/:idSoft-delete a lead (requires confirm)
Destructive : soft-delete a lead. Soft-delete a lead, it disappears from the pipeline (restorable by support only). Only call after the user reviewed preview_delete_crm_lead output and explicitly approved.
Protected: send {"dry_run": true} to preview, then {"confirm": true} to execute (protocol).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string | yes | |
dry_run | body | boolean | true = preview only (nothing happens) | |
confirm | body | boolean | true = execute. Send exactly one of dry_run / confirm |
Request
curl -s -X DELETE "https://api.vendo-ai.com/v1/crm/leads/$LEAD_ID" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"dry_run": true}'Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
GET/v1/crm/leads/:id/branchesPipelines a lead is in
Every pipeline the lead currently sits in, with its stage and owner per pipeline (branch_id null = General). A foreign or unknown lead id answers 404 (it answered 200 with an empty list).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | Lead id |
Request
curl -s "https://api.vendo-ai.com/v1/crm/leads/$LEAD_ID/branches" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"branches": [
{
"branch_id": null,
"branch_name": "General",
"stage_id": "c2b7e9a4-5d1f-4c8e-a3b6-9f0e1d2c3b4a",
"stage_name": "New",
"owner_id": "5e6f7a8b-9c0d-4e1f-8a2b-3c4d5e6f7a8b"
},
{
"branch_id": "8a1d4c3b-2e5f-4a6b-9d7c-1e2f3a4b5c6d",
"branch_name": "Enterprise",
"stage_id": "3f9c2a1e-7b4d-4e2a-9c1f-2d8e5a6b7c90",
"stage_name": "New",
"owner_id": "5e6f7a8b-9c0d-4e1f-8a2b-3c4d5e6f7a8b"
}
]
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | Lead does not exist or belongs to another workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
POST/v1/crm/leads/:id/branchesAdd a lead to another pipeline
#post-v1-crm-leads-id-branches
Places the lead in another pipeline as well (entry stage); it stays where it is. The branch and the optional owner are checked against this workspace.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | Lead id |
branch_id | body | string | yes | "general" or a branch id |
assignee_user_id | body | uuid | Owner in that pipeline (default: current owner) |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/crm/leads/$LEAD_ID/branches" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"branch_id": "8a1d4c3b-2e5f-4a6b-9d7c-1e2f3a4b5c6d",
"assignee_user_id": "5e6f7a8b-9c0d-4e1f-8a2b-3c4d5e6f7a8b"
}'Response
{
"data": {
"ok": true,
"branches": [
{
"branch_id": null,
"branch_name": "General",
"stage_id": "c2b7e9a4-5d1f-4c8e-a3b6-9f0e1d2c3b4a",
"stage_name": "New",
"owner_id": "5e6f7a8b-9c0d-4e1f-8a2b-3c4d5e6f7a8b"
},
{
"branch_id": "8a1d4c3b-2e5f-4a6b-9d7c-1e2f3a4b5c6d",
"branch_name": "Enterprise",
"stage_id": "3f9c2a1e-7b4d-4e2a-9c1f-2d8e5a6b7c90",
"stage_name": "New",
"owner_id": "5e6f7a8b-9c0d-4e1f-8a2b-3c4d5e6f7a8b"
}
]
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | Lead or branch does not exist in this workspace |
| 400 | invalid_request | assignee_user_id is not a workspace member |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
POST/v1/crm/leads/:id/branches/moveMove a lead between pipelines
#post-v1-crm-leads-id-branches-move
Moves the lead out of one pipeline into another, to its entry stage or to to_stage_id (a stage of the destination). Reversible by moving back.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | Lead id |
from_branch_id | body | string | yes | "general" or a branch id |
to_branch_id | body | string | yes | "general" or a branch id |
to_stage_id | body | uuid | Destination stage | |
assignee_user_id | body | uuid | Owner in the destination |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/crm/leads/$LEAD_ID/branches/move" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"from_branch_id": "general", "to_branch_id": "8a1d4c3b-2e5f-4a6b-9d7c-1e2f3a4b5c6d"}'Response
{
"data": {
"ok": true,
"branches": [
{
"branch_id": "8a1d4c3b-2e5f-4a6b-9d7c-1e2f3a4b5c6d",
"branch_name": "Enterprise",
"stage_id": "3f9c2a1e-7b4d-4e2a-9c1f-2d8e5a6b7c90",
"stage_name": "New",
"owner_id": "5e6f7a8b-9c0d-4e1f-8a2b-3c4d5e6f7a8b"
}
]
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | Lead or branch does not exist in this workspace |
| 400 | invalid_request | Same pipeline, stage not in destination, or assignee not a member |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
POST/v1/crm/leads/:id/commentsInternal team note on the lead card
#post-v1-crm-leads-id-comments
Internal team note on the lead card (not visible to the lead). Mentioned users (ids from crm_list_workspace_members) get a CRM notification.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string | yes | |
body | body | string | yes | min length 1; max length 8000 |
mentions | body | string[] | User ids to @mention |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/crm/leads/$LEAD_ID/comments" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"body": "<body>"}'Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: crm_add_comment
GET/v1/crm/leads/:id/conversationFull message thread on the lead's source channel
#get-v1-crm-leads-id-conversation
The full message thread with this lead on their source channel (LinkedIn / email / widget / telegram / phone transcript). Read-only.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string | yes |
Request
curl -s "https://api.vendo-ai.com/v1/crm/leads/$LEAD_ID/conversation" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: crm_get_conversation
POST/v1/crm/leads/:id/emailSend an email from the lead card
Sends a REAL email to the lead (or to "to") from "<your name> via Vendo AI" with replies going to your own address, and logs it on the card. dry_run returns the exact draft; confirm sends. Workspace members need the crm-contact-details permission.
Protected: send {"dry_run": true} to preview, then {"confirm": true} to execute (protocol).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | Lead id |
subject | body | string | yes | ≤500 chars |
body | body | string | yes | Plain-text message |
to | body | string | Recipient; default the lead's email | |
dry_run | body | boolean | true = preview only, nothing changes | |
confirm | body | boolean | true = execute. Exactly one of dry_run / confirm is required |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/crm/leads/$LEAD_ID/email" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"subject": "Following up on our chat",
"body": "Hi Amelia,\n\nAs promised, here is the deck…",
"confirm": true
}'Response
{
"data": {
"ok": true,
"conversation": {
"channel": "email",
"messages": [
{
"direction": "outbound",
"subject": "Following up on our chat",
"sent_at": "2026-10-26T09:14:22.000Z"
}
]
}
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | confirmation_required | Neither dry_run nor confirm was sent |
| 400 | bad_request | Both dry_run and confirm were sent |
| 404 | not_found | Lead does not exist in this workspace |
| 400 | invalid_request | Missing subject/body or no valid recipient |
| 403 | forbidden | Contact details are hidden for your role |
| 502 | send_failed | The mail provider rejected the message |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
POST/v1/crm/leads/:id/reassignReassign a lead
#post-v1-crm-leads-id-reassign
Changes the lead owner (the new owner is notified). Body is now an explicit allow-list: assignee_user_id (public name; to_user_id also accepted) and optional reason. The MCP tool previously sent the forbidden owner_id field and always failed.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | Lead id |
assignee_user_id | body | uuid | yes | New owner from GET /v1/crm/workspace-members |
reason | body | string | ≤255 |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/crm/leads/$LEAD_ID/reassign" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"assignee_user_id": "5e6f7a8b-9c0d-4e1f-8a2b-3c4d5e6f7a8b",
"reason": "Enterprise account"
}'Response
{
"data": {
"id": "c2b7e9a4-5d1f-4c8e-a3b6-9f0e1d2c3b4a",
"assigned_user_id": "5e6f7a8b-9c0d-4e1f-8a2b-3c4d5e6f7a8b"
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | Missing/invalid assignee or not a workspace member |
| 404 | not_found | Lead does not exist in this workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: reassign_crm_lead
POST/v1/crm/leads/:id/replySends a real message to the lead on their source channel (LinkedIn or email). Lead auto-moves to Manager Dialog stage
Send a LinkedIn message or email reply to the lead from the CRM card, real outbound communication under the customer's own accounts. Sends a REAL LinkedIn message or email reply to the lead on their source channel, from the customer's own account. Also moves the lead to the "Manager Dialog" stage. ALWAYS show the drafted text to the human and get approval BEFORE calling this.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string | yes | |
text | body | string | yes | min length 1 |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/crm/leads/$LEAD_ID/reply" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"text": "<text>"}'Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: crm_reply_to_lead
PATCH/v1/crm/leads/:id/stageMove lead to another stage
Drag-and-drop equivalent: move a lead to a different pipeline stage by stage_id (get ids from crm_list_stages). Reversible.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string | yes | |
stage_id | body | string | yes |
Request
curl -s -X PATCH "https://api.vendo-ai.com/v1/crm/leads/$LEAD_ID/stage" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"stage_id": "<stage_id>"}'Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: crm_move_lead
Pipelines (branches) & stages 11
GET/v1/crm/branchesList pipelines (branches)
Branches are isolated pipelines inside one workspace (teams / directions). Lists the pipelines you can see; see GET /v1/crm/branches/all for the full list with stages. branches = isolated pipelines inside one workspace (teams / directions). The assistant must know them before quoting numbers. Branches are isolated pipelines inside one workspace (teams / directions / clients). Returns id, name, is_default ("General"), member_user_ids. Read-only. Call before crm_stats / crm_list_leads when the user has more than one branch, then pass branch=<id> (or "general").
Parameters
No parameters.
Request
curl -s "https://api.vendo-ai.com/v1/crm/branches" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: crm_list_branches
POST/v1/crm/branchesCreate a pipeline
Creates an isolated pipeline (team / direction / client) seeded with its own default stages. member_user_ids limits who sees it (General stays open to everyone). Workspace admins and owner only.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
name | body | string | yes | 1..120 chars |
member_user_ids | body | uuid[] | User ids from GET /v1/crm/workspace-members |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/crm/branches" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name": "Enterprise", "member_user_ids": ["5e6f7a8b-9c0d-4e1f-8a2b-3c4d5e6f7a8b"]}'Response
{
"data": {
"id": "8a1d4c3b-2e5f-4a6b-9d7c-1e2f3a4b5c6d",
"name": "Enterprise",
"is_default": false,
"member_user_ids": [
"5e6f7a8b-9c0d-4e1f-8a2b-3c4d5e6f7a8b"
]
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | name missing |
| 403 | forbidden | Caller is not a workspace admin/owner |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: crm_create_branch
GET/v1/crm/branches/allList all pipelines
Every pipeline (branch) of the workspace with id, name and is_default, wider than GET /v1/crm/branches for restricted members. Use it to pick a destination when adding or moving a lead.
Parameters
No parameters.
Request
curl -s "https://api.vendo-ai.com/v1/crm/branches/all" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"branches": [
{
"id": "3f9c2a1e-7b4d-4e2a-9c1f-2d8e5a6b7c90",
"name": "General",
"is_default": true
},
{
"id": "8a1d4c3b-2e5f-4a6b-9d7c-1e2f3a4b5c6d",
"name": "Enterprise",
"is_default": false
}
]
}
}Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
GET/v1/crm/branches/:idGet one pipeline
One pipeline by id (id, name, is_default).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | Branch id |
Request
curl -s "https://api.vendo-ai.com/v1/crm/branches/$BRANCH_ID" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"branch": {
"id": "8a1d4c3b-2e5f-4a6b-9d7c-1e2f3a4b5c6d",
"name": "Enterprise",
"is_default": false
}
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | Branch does not exist in this workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: crm_get_branch
PATCH/v1/crm/branches/:idRename a pipeline / change members
Renames a pipeline and/or replaces its member list. The General pipeline cannot be renamed or restricted. Admins and owner only.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | Branch id |
name | body | string | New name | |
member_user_ids | body | uuid[] | Replaces the member list |
Request
curl -s -X PATCH "https://api.vendo-ai.com/v1/crm/branches/$BRANCH_ID" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name": "Enterprise EMEA"}'Response
{
"data": {
"ok": true,
"branch": {
"id": "8a1d4c3b-2e5f-4a6b-9d7c-1e2f3a4b5c6d",
"name": "Enterprise EMEA",
"is_default": false
}
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | Branch does not exist in this workspace |
| 400 | invalid_request | Nothing to update, or General branch |
| 403 | forbidden | Caller is not a workspace admin/owner |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: crm_update_branch
DELETE/v1/crm/branches/:idDelete a pipeline
Deletes a non-default pipeline with its stages and member list; its leads are not deleted, they fall back to General. dry_run shows the pipeline and its lead count. Admins and owner only.
Protected: send {"dry_run": true} to preview, then {"confirm": true} to execute (protocol).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | Branch id |
dry_run | body | boolean | true = preview only, nothing changes | |
confirm | body | boolean | true = execute. Exactly one of dry_run / confirm is required |
Request
curl -s -X DELETE "https://api.vendo-ai.com/v1/crm/branches/$BRANCH_ID" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"dry_run": true}'Response
{
"data": {
"dry_run": true,
"would_do": "Delete the pipeline \"Enterprise\". Its own stages and member list are removed; its leads are NOT deleted — they fall back to the General pipeline. Only workspace admins/owner may do this. Cannot be undone.",
"estimated": {
"branch": {
"id": "8a1d4c3b-2e5f-4a6b-9d7c-1e2f3a4b5c6d",
"name": "Enterprise",
"is_default": false
},
"leads_in_branch": 37,
"is_default": false
}
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | confirmation_required | Neither dry_run nor confirm was sent |
| 400 | bad_request | Both dry_run and confirm were sent |
| 404 | not_found | Branch does not exist in this workspace |
| 400 | invalid_request | General branch cannot be deleted |
| 403 | forbidden | Caller is not a workspace admin/owner |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
GET/v1/crm/pipeline-stagesList pipeline stages
Ordered stages of one pipeline. New: ?branch ("general" or a branch id), before, only the General pipeline's stages were reachable.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
branch | query | string | "general" (default) or a branch id |
Request
curl -s "https://api.vendo-ai.com/v1/crm/pipeline-stages" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"stages": [
{
"id": "c2b7e9a4-5d1f-4c8e-a3b6-9f0e1d2c3b4a",
"name": "New",
"color": "#3b82f6",
"order_index": 0,
"is_terminal": false
}
]
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | branch is empty, malformed or not in this workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: crm_list_stages
POST/v1/crm/pipeline-stagesCreate a custom stage (inserted before Won/Lost)
Add a custom kanban column to a pipeline (name, optional hex color, is_terminal for Won/Lost-like end stages). Inserted before the terminal stages. branch = "general" (default) or a branch id.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
name | body | string | yes | min length 1 |
color | body | string | #RRGGBB | |
is_terminal | body | boolean | ||
branch | body | string | "general" (default) or a branch id |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/crm/pipeline-stages" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name": "<name>"}'Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: crm_create_stage
POST/v1/crm/pipeline-stages/reorderReorder pipeline stages
#post-v1-crm-pipeline-stages-reorder
Sets the full left-to-right order of one pipeline's stages (the column drag in the UI). stage_ids must list every stage of that branch exactly once; the order applies to everyone in the workspace. Fixed (2026-10-26): an incomplete, foreign or duplicated list answers 400 with the missing / unknown ids (it answered 502); the order is left unchanged.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
stage_ids | body | uuid[] | yes | All stage ids of the branch in the new order |
branch | body | string | "general" (default) or a branch id |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/crm/pipeline-stages/reorder" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"stage_ids": [
"c2b7e9a4-5d1f-4c8e-a3b6-9f0e1d2c3b4a",
"5e6f7a8b-9c0d-4e1f-8a2b-3c4d5e6f7a8b",
"3f9c2a1e-7b4d-4e2a-9c1f-2d8e5a6b7c90"
],
"branch": "general"
}'Response
{
"data": {
"stages": [
{
"id": "c2b7e9a4-5d1f-4c8e-a3b6-9f0e1d2c3b4a",
"name": "New",
"color": "#3b82f6",
"order_index": 0,
"is_terminal": false
}
]
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | stage_ids is not exactly the full set of stages of that pipeline (the message lists missing, foreign and duplicated ids) |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: crm_reorder_stages
PATCH/v1/crm/pipeline-stages/:idUpdate a pipeline stage
#patch-v1-crm-pipeline-stages-id
Renames / recolours a stage or changes is_terminal. Fixed (2026-10-26): a foreign or unknown stage id answers 404 not_found (it answered 400 with code internal_error).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | Stage id from GET /v1/crm/pipeline-stages |
name | body | string | ||
color | body | string | Hex colour | |
is_terminal | body | boolean |
Request
curl -s -X PATCH "https://api.vendo-ai.com/v1/crm/pipeline-stages/$STAGE_ID" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name": "Proposal sent", "color": "#046bd2"}'Response
{
"data": {
"id": "5b8e1c3a-7d2f-4a9e-8c6b-0e4d2f1a9b57",
"name": "Proposal sent",
"color": "#046bd2",
"order_index": 3,
"is_terminal": false,
"branch_id": null
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | Stage does not exist or is not in your workspace |
| 400 | invalid_request | Invalid value |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: crm_update_stage
DELETE/v1/crm/pipeline-stages/:idDelete a pipeline stage
#delete-v1-crm-pipeline-stages-id
Deletes a stage. Refused when it still holds leads or is a system stage (New / Manager Dialog). Destructive protocol. Fixed (2026-10-26): the dry run checks the stage and names it; a foreign or unknown stage id answers 404 for dry_run and confirm (it answered 200 / 400).
Protected: send {"dry_run": true} to preview, then {"confirm": true} to execute (protocol).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | Stage id |
dry_run | body | boolean | true = preview only: nothing is changed (HTTP 200) | |
confirm | body | boolean | true = execute. Send exactly one of dry_run / confirm |
Request
curl -s -X DELETE "https://api.vendo-ai.com/v1/crm/pipeline-stages/$STAGE_ID" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"dry_run": true}'Response
{
"data": {
"dry_run": true,
"would_do": "Delete the pipeline stage \"Proposal sent\". Refused downstream if the stage still contains leads or is a system stage (New / Manager Dialog).",
"stage": {
"id": "5b8e1c3a-7d2f-4a9e-8c6b-0e4d2f1a9b57",
"name": "Proposal sent",
"branch_id": null,
"is_terminal": false
}
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | confirmation_required | Neither dry_run nor confirm was sent (or both) |
| 404 | not_found | Stage does not exist or is not in your workspace |
| 400 | invalid_request | Stage still has leads or is a system stage |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
Lead documents 4
GET/v1/crm/leads/:id/documentsList lead documents
#get-v1-crm-leads-id-documents
Files attached to the lead card, newest first.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | Lead id |
Request
curl -s "https://api.vendo-ai.com/v1/crm/leads/$LEAD_ID/documents" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"documents": [
{
"id": "5e6f7a8b-9c0d-4e1f-8a2b-3c4d5e6f7a8b",
"file_name": "proposal-ledgerly.pdf",
"mime_type": "application/pdf",
"size_bytes": 284113,
"uploaded_by": "3f9c2a1e-7b4d-4e2a-9c1f-2d8e5a6b7c90",
"uploaded_by_name": "Sam Carter",
"created_at": "2026-10-26T09:14:22.000Z"
}
]
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | Lead does not exist in this workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
POST/v1/crm/leads/:id/documentsUpload lead documents
#post-v1-crm-leads-id-documents
Attaches files to the lead card (private storage, activity entry). multipart field "files" (≤10 files × 25 MB) or JSON { files: [{ filename, content_base64 } | { file_url, filename? }] }. Allowed: pdf, doc(x), xls(x), csv, txt, rtf, odt, ods, ppt(x), png, jpg/jpeg, gif, webp, heic, zip.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | Lead id |
files | body | file[] | array | yes | Multipart files, or JSON array of { filename, content_base64 } / { file_url } |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/crm/leads/$LEAD_ID/documents" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"files": [{"filename": "proposal-ledgerly.pdf", "content_base64": "JVBERi0xLjcKJ…"}]}'Response
{
"data": {
"documents": [
{
"id": "5e6f7a8b-9c0d-4e1f-8a2b-3c4d5e6f7a8b",
"file_name": "proposal-ledgerly.pdf",
"mime_type": "application/pdf",
"size_bytes": 284113,
"uploaded_by": "3f9c2a1e-7b4d-4e2a-9c1f-2d8e5a6b7c90",
"uploaded_by_name": "Sam Carter",
"created_at": "2026-10-26T09:14:22.000Z"
}
]
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | Lead does not exist in this workspace |
| 400 | invalid_request | No files or a file type is not accepted |
| 413 | payload_too_large | File or body too large |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
DELETE/v1/crm/leads/:id/documents/:docIdDelete a lead document
#delete-v1-crm-leads-id-documents-docid
Permanently deletes the file from the lead card and storage. dry_run shows which file.
Protected: send {"dry_run": true} to preview, then {"confirm": true} to execute (protocol).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | Lead id |
docId | path | uuid | yes | Document id |
dry_run | body | boolean | true = preview only, nothing changes | |
confirm | body | boolean | true = execute. Exactly one of dry_run / confirm is required |
Request
curl -s -X DELETE "https://api.vendo-ai.com/v1/crm/leads/$LEAD_ID/documents/$DOC_ID" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"confirm": true}'Response
{
"data": {
"ok": true
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | confirmation_required | Neither dry_run nor confirm was sent |
| 400 | bad_request | Both dry_run and confirm were sent |
| 404 | not_found | Document does not exist in this workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: preview_delete_crm_lead_document, delete_crm_lead_document
GET/v1/crm/leads/:id/documents/:docId/fileDownload a lead document
#get-v1-crm-leads-id-documents-docid-file
Streams the file (PDF and images inline, everything else as attachment). ?format=base64 returns JSON { file_name, mime_type, size_bytes, content_base64 } for files up to 5 MB (used by MCP).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | Lead id |
docId | path | uuid | yes | Document id |
format | query | string | "base64" for a JSON envelope one of: base64 |
Request
curl -s "https://api.vendo-ai.com/v1/crm/leads/$LEAD_ID/documents/$DOC_ID/file" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"file_name": "proposal-ledgerly.pdf",
"mime_type": "application/pdf",
"size_bytes": 284113,
"content_base64": "JVBERi0xLjcKJ…"
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | Document does not exist in this workspace |
| 413 | payload_too_large | format=base64 on a file over 5 MB |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
Communications 4
GET/v1/crm/communications/cardsCommunication cards
#get-v1-crm-communications-cards
Read-only outbound stream (everyone contacted, replied or not). SECURITY FIX: now served through the same path as the app, so emails and phones are masked (null + contact_masked=true) for members without the "crm-contact-details" permission; it used to return them unmasked.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
limit | query | integer | 1..500 |
Request
curl -s "https://api.vendo-ai.com/v1/crm/communications/cards" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"cards": [
{
"type": "sdr",
"id": "c2b7e9a4-5d1f-4c8e-a3b6-9f0e1d2c3b4a",
"name": "UK fintech funding",
"sent": 640,
"replied": 29,
"last_activity_at": "2026-10-26T09:14:22.000Z"
}
]
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | Bad type, id or limit |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
GET/v1/crm/communications/cards/:type/:id/historyOutbound history of a card
#get-v1-crm-communications-cards-type-id-history
Read-only outbound stream (everyone contacted, replied or not). SECURITY FIX: now served through the same path as the app, so emails and phones are masked (null + contact_masked=true) for members without the "crm-contact-details" permission; it used to return them unmasked.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
type | path | string | yes | one of: campaign, sdr |
id | path | uuid | yes | Campaign or SDR search id |
channel | query | string | email|linkedin|phone | |
replied | query | boolean | ||
from | query | date | ||
to | query | date | ||
limit | query | integer | ||
offset | query | integer |
Request
curl -s "https://api.vendo-ai.com/v1/crm/communications/cards/$CARD_TYPE/$ID/history" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"rows": [
{
"contact_name": "Tom Reyes",
"email": null,
"contact_masked": true,
"channel": "email",
"replied": true,
"sent_at": "2026-10-26T09:14:22.000Z",
"key": "tom@brightpay.io"
}
],
"total": 1
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | Bad type, id or limit |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
GET/v1/crm/communications/cards/:type/:id/threadMessage thread of one contact
#get-v1-crm-communications-cards-type-id-thread
Read-only outbound stream (everyone contacted, replied or not). SECURITY FIX: now served through the same path as the app, so emails and phones are masked (null + contact_masked=true) for members without the "crm-contact-details" permission; it used to return them unmasked. The card id must be one of your campaigns (type campaign) or SDR searches (type sdr); otherwise 404 (the id used to be ignored).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
type | path | string | yes | one of: campaign, sdr |
id | path | uuid | yes | |
key | query | string | yes | Thread key from a history row |
Request
curl -s "https://api.vendo-ai.com/v1/crm/communications/cards/$CARD_TYPE/$ID/thread?key=$KEY" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"rows": [
{
"contact_name": "Tom Reyes",
"email": null,
"contact_masked": true,
"channel": "email",
"replied": true,
"sent_at": "2026-10-26T09:14:22.000Z",
"key": "tom@brightpay.io"
}
],
"total": 1
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | Bad type, id or limit |
| 404 | not_found | The card (campaign or SDR search) does not exist or belongs to another workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
POST/v1/crm/communications/promotePromote a contact to a lead
#post-v1-crm-communications-promote
Turns a contacted person from the Communications stream into a CRM lead in the New stage (manual source). Deduplicated per workspace by email / LinkedIn / phone: returns the existing lead with already_existed=true.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
email | body | string | At least one of email / linkedin_url / phone | |
linkedin_url | body | string | http(s) URL | |
phone | body | string | ||
contact_name | body | string | Full name | |
company_name | body | string | ||
title | body | string | ||
avatar_url | body | string | ||
assigned_user_id | body | uuid | Owner (default: you) |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/crm/communications/promote" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"email": "tom@brightpay.io", "contact_name": "Tom Reyes", "company_name": "BrightPay"}'Response
{
"data": {
"lead_id": "c2b7e9a4-5d1f-4c8e-a3b6-9f0e1d2c3b4a",
"already_existed": false
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | No email/linkedin_url/phone, or assignee not a member |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
Calendar 4
GET/v1/crm/calendar/eventsCustom calendar events. Optional from / to (ISO dates)
Custom events only. SDR sending windows also appear in the UI calendar, but they are derived from the search schedule and are not editable, read them via instead. List custom calendar events (meetings, reminders) in the CRM planner for a date range. Note: SDR sending windows also appear in the UI calendar but are derived from the search schedule, read those via GET /v1/sdr/searches/:idsdr_get_search. Read-only, free.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
from | query | string | ISO date, start of range | |
to | query | string | ISO date, end of range |
Request
curl -s "https://api.vendo-ai.com/v1/crm/calendar/events" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
POST/v1/crm/calendar/eventsCreate a calendar event
Creates a custom CRM calendar event (call, meeting, task…) optionally linked to a lead. Answers 201. Fixed (2026-10-26): a lead_id that is not a lead of your workspace answers 404 (it was silently dropped and the event created unlinked).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
title | body | string | yes | Event title |
start_at | body | datetime | yes | ISO 8601 |
end_at | body | datetime | ISO 8601 | |
lead_id | body | uuid | Lead to link (GET /v1/crm/leads) | |
description | body | string |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/crm/calendar/events" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"title": "Call Sarah (Northgate Dental)",
"start_at": "2026-10-28T10:00:00Z",
"end_at": "2026-10-28T10:30:00Z",
"lead_id": "2c9e7a4b-1d3f-4b8e-9a6c-5f0d2e1b8a37"
}'Response
{
"data": {
"id": "9a4e2c7b-3d1f-4e8a-b6c5-0f2d9e1a7b48",
"title": "Call Sarah (Northgate Dental)",
"start_at": "2026-10-28T10:00:00.000Z",
"end_at": "2026-10-28T10:30:00.000Z",
"lead_id": "2c9e7a4b-1d3f-4b8e-9a6c-5f0d2e1b8a37",
"created_at": "2026-10-26T14:10:00.000Z"
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | Missing title/start_at or lead_id is not a UUID |
| 404 | not_found | lead_id is not a lead of your workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
PATCH/v1/crm/calendar/events/:idUpdate a calendar event
#patch-v1-crm-calendar-events-id
Updates a custom calendar event. Fixed (2026-10-26): a foreign or unknown event id answers 404 (it answered 400 "Event not found"); a lead_id that is not a lead of your workspace answers 404 (it used to unlink the event's real lead silently).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | Event id |
title | body | string | ||
start_at | body | datetime | ||
end_at | body | datetime | ||
lead_id | body | uuid | Lead to link; null unlinks |
Request
curl -s -X PATCH "https://api.vendo-ai.com/v1/crm/calendar/events/$EVENT_ID" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"start_at": "2026-10-28T11:00:00Z"}'Response
{
"data": {
"id": "9a4e2c7b-3d1f-4e8a-b6c5-0f2d9e1a7b48",
"title": "Call Sarah (Northgate Dental)",
"start_at": "2026-10-28T11:00:00.000Z",
"lead_id": "2c9e7a4b-1d3f-4b8e-9a6c-5f0d2e1b8a37"
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | Event or lead_id does not exist or is not in your workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
DELETE/v1/crm/calendar/events/:idDelete a calendar event. No confirmation step required
#delete-v1-crm-calendar-events-id
Not gated by dry_run/confirm: a calendar event is a low-value, easily recreated record, unlike a lead or a pipeline stage, deleting one destroys no history and cannot cascade. Remove a custom calendar event. Unlike deleting a lead or a stage this destroys no history and needs no preview step, but it cannot be undone.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | string | yes |
Request
curl -s -X DELETE "https://api.vendo-ai.com/v1/crm/calendar/events/$EVENT_ID" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
CRM notifications 3
GET/v1/crm/notificationsList CRM notifications
Latest 50 CRM notifications of the caller (assigned, reassigned, mentioned, new_lead, stage_change) with total and unread counts. unread_only=true now really filters (it was ignored downstream); the unused limit parameter was removed.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
unread_only | query | boolean | Only unread |
Request
curl -s "https://api.vendo-ai.com/v1/crm/notifications" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"total": 58,
"unread": 4,
"rows": [
{
"id": "3f9c2a1e-7b4d-4e2a-9c1f-2d8e5a6b7c90",
"type": "new_lead",
"lead_id": "c2b7e9a4-5d1f-4c8e-a3b6-9f0e1d2c3b4a",
"payload": {
"lead_name": "Tom Reyes",
"source": "email_reply_real"
},
"read_at": null,
"created_at": "2026-10-26T09:14:22.000Z"
}
]
}
}Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
POST/v1/crm/notifications/read-allMark all notifications as read
#post-v1-crm-notifications-read-all
Clear the CRM notification badge by marking everything read.
Parameters
No parameters.
Request
curl -s -X POST "https://api.vendo-ai.com/v1/crm/notifications/read-all" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
PATCH/v1/crm/notifications/:id/readMark a CRM notification read
#patch-v1-crm-notifications-id-read
Marks one CRM notification as read. Fixed (2026-10-26): the id must be one of your notifications (among the latest 50, or the latest 50 unread), a foreign or unknown id answers 404 (it answered 200 {ok:true}); an already-read notification is left unchanged (idempotent) and answers already_read: true.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | Notification id from GET /v1/crm/notifications |
Request
curl -s -X PATCH "https://api.vendo-ai.com/v1/crm/notifications/$NOTIFICATION_ID/read" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"ok": true
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | Notification does not exist or is not yours |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
Settings & lookups 6
GET/v1/crm/agentsAI agents in this workspace that leads can be attributed to
List the AI agents in this workspace that CRM leads can be attributed to. Read-only, free.
Parameters
No parameters.
Request
curl -s "https://api.vendo-ai.com/v1/crm/agents" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
GET/v1/crm/linkedin-accountsLinkedIn accounts connected to this workspace, as shown in the CRM
List the LinkedIn accounts connected to this workspace, as shown in the CRM. Useful to see which account owns a conversation. Read-only, free.
Parameters
No parameters.
Request
curl -s "https://api.vendo-ai.com/v1/crm/linkedin-accounts" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
GET/v1/crm/settingsRead CRM settings, including the notify_* toggles
These control whether Vendo emails/notifies on new leads, replies and stage changes. Exposed so an integration can mute notifications while it drives the CRM itself. Read CRM settings, including the notify_* toggles that control which events send an email/notification. Read-only, free.
Parameters
No parameters.
Request
curl -s "https://api.vendo-ai.com/v1/crm/settings" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: crm_get_settings
PATCH/v1/crm/settingsUpdate CRM settings
Workspace CRM settings with an explicit, type-checked allow-list of the real fields: notify_new_lead, notify_reassigned, notify_mention, notify_email, notify_in_app, stale_alert_days (1..90), default_date_range, excluded_agent_ids, excluded_sources, excluded_linkedin_account_ids. Unknown fields are rejected instead of being merged into the settings row.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
notify_new_lead | body | boolean | ||
notify_reassigned | body | boolean | ||
notify_mention | body | boolean | ||
notify_email | body | boolean | ||
notify_in_app | body | boolean | ||
stale_alert_days | body | integer | 1..90 | |
default_date_range | body | string | one of: 24h, 7d, 30d, 90d, 180d, 1y, all | |
excluded_agent_ids | body | uuid[] | ||
excluded_sources | body | string[] | ||
excluded_linkedin_account_ids | body | uuid[] |
Request
curl -s -X PATCH "https://api.vendo-ai.com/v1/crm/settings" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"notify_email": false, "stale_alert_days": 7}'Response
{
"data": {
"workspace_id": "8a1d4c3b-2e5f-4a6b-9d7c-1e2f3a4b5c6d",
"excluded_agent_ids": [],
"excluded_sources": [],
"excluded_linkedin_account_ids": [],
"default_date_range": "30d",
"notify_new_lead": true,
"notify_reassigned": true,
"notify_mention": true,
"notify_email": false,
"notify_in_app": true,
"stale_alert_days": 7
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | Wrong type, out of range or nothing to update |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: crm_update_settings
GET/v1/crm/statsPer-stage counts, new today/this week, stale leads
Counts per stage, new today/this week, stale leads. Read-only. Optional branch ("general" or a branch id from crm_list_branches), omitted = all branches visible to the user. If the workspace has more than one branch, call crm_list_branches first and report per branch or ask which one.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
branch | query | string |
Request
curl -s "https://api.vendo-ai.com/v1/crm/stats" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: crm_stats
GET/v1/crm/workspace-membersPeople who can own a lead. Call this before reassign to get valid owner ids
POST /leads/:id/reassign shipped without any way to discover a valid assignee, so callers were reassigning blind. These three make it usable. List the people in this workspace who can own a CRM lead. Use before reassign_crm_lead to get valid owner ids. Read-only, free.
Parameters
No parameters.
Request
curl -s "https://api.vendo-ai.com/v1/crm/workspace-members" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
Workspace & account API 30
Team and invitations, your profile and notification preferences, in-app notifications, CRM integrations and the referral program.
Team 6
GET/v1/workspaceGet workspace and team
Workspace name and owner, members with role and feature permissions, pending invitations, and whether the caller is admin / owner. feature_ids lists the values accepted by the permissions route.
Parameters
No parameters.
Request
curl -s "https://api.vendo-ai.com/v1/workspace" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Show the full response (62 lines)
{
"data": {
"workspace": {
"id": "8a1d4c3b-2e5f-4a6b-9d7c-1e2f3a4b5c6d",
"name": "Sam's Workspace",
"owner_id": "3f9c2a1e-7b4d-4e2a-9c1f-2d8e5a6b7c90",
"created_at": "2026-10-26T09:14:22.000Z",
"updated_at": "2026-10-26T09:14:22.000Z"
},
"members": [
{
"id": "8a1d4c3b-2e5f-4a6b-9d7c-1e2f3a4b5c6d",
"user_id": "3f9c2a1e-7b4d-4e2a-9c1f-2d8e5a6b7c90",
"email": "sam@ledgerly.co.uk",
"first_name": "Sam",
"last_name": "Carter",
"avatar_url": null,
"role": "admin",
"permissions": null,
"joined_at": "2026-10-26T09:14:22.000Z"
},
{
"id": "c2b7e9a4-5d1f-4c8e-a3b6-9f0e1d2c3b4a",
"user_id": "5e6f7a8b-9c0d-4e1f-8a2b-3c4d5e6f7a8b",
"email": "priya@ledgerly.co.uk",
"first_name": "Priya",
"last_name": "Shah",
"avatar_url": null,
"role": "member",
"permissions": [
"crm",
"analytics"
],
"joined_at": "2026-10-26T09:14:22.000Z"
}
],
"pendingInvitations": [
{
"id": "3f9c2a1e-7b4d-4e2a-9c1f-2d8e5a6b7c90",
"email": "li@ledgerly.co.uk",
"status": "pending",
"created_at": "2026-10-26T09:14:22.000Z",
"expires_at": "2026-11-02T09:14:22.000Z"
}
],
"isAdmin": true,
"amOwner": true,
"myPermissions": null,
"feature_ids": [
"dashboard",
"my-agents",
"sales-process",
"vendo-sdr",
"audiences",
"crm",
"analytics",
"billing",
"settings",
"crm-contact-details"
]
}
}Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: get_workspace
POST/v1/workspace/invitationsInvite a teammate
#post-v1-workspace-invitations
Sends a real invitation email (link valid 7 days); on accepting, the person joins with full access. dry_run checks whether the invite is possible without sending. Admins only.
Protected: send {"dry_run": true} to preview, then {"confirm": true} to execute (protocol).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
email | body | string | yes | Invitee email |
dry_run | body | boolean | true = preview only, nothing changes | |
confirm | body | boolean | true = execute. Exactly one of dry_run / confirm is required |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/workspace/invitations" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"email": "li@ledgerly.co.uk", "confirm": true}'Response
{
"data": {
"success": true,
"message": "Invitation sent to li@ledgerly.co.uk",
"invitation": {
"id": "3f9c2a1e-7b4d-4e2a-9c1f-2d8e5a6b7c90",
"email": "li@ledgerly.co.uk",
"expires_at": "2026-11-02T09:14:22.000Z"
}
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | confirmation_required | Neither dry_run nor confirm was sent |
| 400 | bad_request | Both dry_run and confirm were sent |
| 400 | invalid_request | Invalid email, already a member, or invitation pending |
| 403 | forbidden | Only workspace admins can invite |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: preview_invite_workspace_member, invite_workspace_member
DELETE/v1/workspace/invitations/:idCancel an invitation
#delete-v1-workspace-invitations-id
Cancels a pending invitation; its link stops working. Admins only.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | Invitation id |
Request
curl -s -X DELETE "https://api.vendo-ai.com/v1/workspace/invitations/$INVITATION_ID" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"success": true,
"message": "Invitation cancelled"
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | Invitation does not exist in this workspace |
| 403 | forbidden | Only workspace admins can cancel invitations |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
DELETE/v1/workspace/members/:userIdRemove a teammate
#delete-v1-workspace-members-userid
Removes a member from the workspace: they immediately lose access to its agents, campaigns, CRM and credits; their account keeps its own new empty workspace. Admins only; dry_run shows who would be removed.
Protected: send {"dry_run": true} to preview, then {"confirm": true} to execute (protocol).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
userId | path | uuid | yes | Member user_id |
dry_run | body | boolean | true = preview only, nothing changes | |
confirm | body | boolean | true = execute. Exactly one of dry_run / confirm is required |
Request
curl -s -X DELETE "https://api.vendo-ai.com/v1/workspace/members/$USER_ID" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"dry_run": true}'Response
{
"data": {
"dry_run": true,
"would_do": "Remove priya@ledgerly.co.uk from \"Sam's Workspace\". They immediately lose access to its agents, campaigns, CRM and credits (their account stays and gets its own empty workspace). Re-adding needs a new invitation.",
"estimated": {
"member": {
"user_id": "5e6f7a8b-9c0d-4e1f-8a2b-3c4d5e6f7a8b",
"email": "priya@ledgerly.co.uk",
"role": "member"
},
"caller_is_admin": true
}
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | confirmation_required | Neither dry_run nor confirm was sent |
| 400 | bad_request | Both dry_run and confirm were sent |
| 404 | not_found | Member does not exist in this workspace |
| 400 | invalid_request | You cannot remove yourself |
| 403 | forbidden | Only workspace admins can remove members |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: preview_remove_workspace_member, remove_workspace_member
PATCH/v1/workspace/members/:userId/permissionsSet a member's feature access
#patch-v1-workspace-members-userid-permissions
Sets the allow-list of features a regular member can use (null = full access). "crm-contact-details" decides whether they see lead emails and phones. Admins only; the owner and admins cannot be restricted.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
userId | path | uuid | yes | Member user_id |
permissions | body | string[] | null | yes | Feature ids or null one of: dashboard, my-agents, sales-process, vendo-sdr, audiences, crm, analytics, billing, settings, crm-contact-details |
Request
curl -s -X PATCH "https://api.vendo-ai.com/v1/workspace/members/$USER_ID/permissions" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"permissions": ["crm", "analytics"]}'Response
{
"data": {
"success": true,
"message": "Member access updated",
"permissions": [
"crm",
"analytics"
]
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | Unknown feature id |
| 403 | forbidden | Caller is not admin, or target is owner/admin |
| 404 | not_found | Member does not exist in this workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
PATCH/v1/workspace/members/:userId/rolePromote / demote an admin
#patch-v1-workspace-members-userid-role
admin=true makes the member an admin (full access, manages team and pipelines); false makes them a regular member. Owner only; dry_run shows the change.
Protected: send {"dry_run": true} to preview, then {"confirm": true} to execute (protocol).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
userId | path | uuid | yes | Member user_id |
admin | body | boolean | yes | true = admin, false = member |
dry_run | body | boolean | true = preview only, nothing changes | |
confirm | body | boolean | true = execute. Exactly one of dry_run / confirm is required |
Request
curl -s -X PATCH "https://api.vendo-ai.com/v1/workspace/members/$USER_ID/role" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"admin": true, "confirm": true}'Response
{
"data": {
"success": true,
"message": "Member promoted to admin",
"admin": true
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | confirmation_required | Neither dry_run nor confirm was sent |
| 400 | bad_request | Both dry_run and confirm were sent |
| 404 | not_found | Member does not exist in this workspace |
| 403 | forbidden | Only the workspace owner can change roles |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: preview_set_workspace_member_role, set_workspace_member_role
Profile & preferences 5
GET/v1/meGet my profile
Profile of the API key owner (no secrets, no 2FA state).
Parameters
No parameters.
Request
curl -s "https://api.vendo-ai.com/v1/me" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"id": "3f9c2a1e-7b4d-4e2a-9c1f-2d8e5a6b7c90",
"email": "sam@ledgerly.co.uk",
"first_name": "Sam",
"last_name": "Carter",
"company_name": "Ledgerly",
"position": "Head of Growth",
"avatar_url": "/uploads/avatars/7d2f….png",
"auth_provider": "local",
"email_verified": true,
"google_calendar_connected": false,
"created_at": "2026-10-26T09:14:22.000Z",
"updated_at": "2026-10-26T09:14:22.000Z"
}
}Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: get_profile
PATCH/v1/meUpdate my profile
Updates first_name (1..100), last_name (≤100), company_name (≤200) and position (≤200). Email, password, 2FA and account deletion stay in the app.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
first_name | body | string | ||
last_name | body | string | ||
company_name | body | string | ||
position | body | string |
Request
curl -s -X PATCH "https://api.vendo-ai.com/v1/me" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"position": "VP Growth"}'Response
{
"data": {
"id": "3f9c2a1e-7b4d-4e2a-9c1f-2d8e5a6b7c90",
"email": "sam@ledgerly.co.uk",
"first_name": "Sam",
"last_name": "Carter",
"company_name": "Ledgerly",
"position": "VP Growth",
"avatar_url": "/uploads/avatars/7d2f….png",
"auth_provider": "local",
"email_verified": true,
"google_calendar_connected": false,
"created_at": "2026-10-26T09:14:22.000Z",
"updated_at": "2026-10-26T09:14:22.000Z"
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | Empty first_name, too long, or nothing to update |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: update_profile
PATCH/v1/me/avatarSet my profile photo
Uploads a JPG/PNG/GIF (≤5 MB) as the profile photo: multipart field "avatar", or JSON { image_url } (fetched server-side) or { content_base64, filename }.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
avatar | body | file (multipart) | Image file | |
image_url | body | string | Public image URL | |
content_base64 | body | string | Base64 image | |
filename | body | string | With content_base64, e.g. me.png |
Request
curl -s -X PATCH "https://api.vendo-ai.com/v1/me/avatar" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"image_url": "https://example.com/photos/sam.png"}'Response
{
"data": {
"id": "3f9c2a1e-7b4d-4e2a-9c1f-2d8e5a6b7c90",
"email": "sam@ledgerly.co.uk",
"first_name": "Sam",
"last_name": "Carter",
"company_name": "Ledgerly",
"position": "Head of Growth",
"avatar_url": "/uploads/avatars/91ab….png",
"auth_provider": "local",
"email_verified": true,
"google_calendar_connected": false,
"created_at": "2026-10-26T09:14:22.000Z",
"updated_at": "2026-10-26T09:14:22.000Z"
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | Not a JPG/PNG/GIF or larger than 5 MB |
| 400 | forbidden_url | image_url resolves to a private address |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: set_profile_avatar
GET/v1/me/notification-preferencesGet my notification preferences
#get-v1-me-notification-preferences
Personal notification switches (Settings › Notifications).
Parameters
No parameters.
Request
curl -s "https://api.vendo-ai.com/v1/me/notification-preferences" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"email_notifications": true,
"in_app_notifications": true,
"meeting_reminders": true,
"credit_alerts": true,
"analytics_emails_enabled": false
}
}Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
PATCH/v1/me/notification-preferencesUpdate my notification preferences
#patch-v1-me-notification-preferences
Turns personal notifications on or off: email_notifications, in_app_notifications, meeting_reminders, credit_alerts, analytics_emails_enabled. Turning them off stops alerts to the human.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
email_notifications | body | boolean | ||
in_app_notifications | body | boolean | ||
meeting_reminders | body | boolean | ||
credit_alerts | body | boolean | ||
analytics_emails_enabled | body | boolean |
Request
curl -s -X PATCH "https://api.vendo-ai.com/v1/me/notification-preferences" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"analytics_emails_enabled": true}'Response
{
"data": {
"email_notifications": true,
"in_app_notifications": true,
"meeting_reminders": true,
"credit_alerts": true,
"analytics_emails_enabled": true
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | Non-boolean value or nothing to update |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
In-app notifications 5
GET/v1/notificationsList in-app notifications
The caller's bell notifications (meetings, billing, low credits, invitations…), newest first, with unread count and paging. CRM lead notifications are separate (GET /v1/crm/notifications).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
page | query | integer | Default 1 | |
limit | query | integer | 1..100, default 20 |
Request
curl -s "https://api.vendo-ai.com/v1/notifications" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"notifications": [
{
"id": "3f9c2a1e-7b4d-4e2a-9c1f-2d8e5a6b7c90",
"type": "LOW_CREDITS",
"subject": "Your credit balance is running low",
"preview": "You have 48 credits left…",
"isRead": false,
"createdAt": "2026-10-26T09:14:22.000Z",
"readAt": null
}
],
"total": 34,
"unreadCount": 3,
"page": 1,
"limit": 20,
"totalPages": 2
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | page/limit out of range |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
POST/v1/notifications/read-allMark all notifications read
#post-v1-notifications-read-all
Marks every unread in-app notification of the caller as read.
Parameters
No parameters.
Request
curl -s -X POST "https://api.vendo-ai.com/v1/notifications/read-all" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"success": true,
"markedCount": 3
}
}Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
GET/v1/notifications/unread-countUnread notifications count
#get-v1-notifications-unread-count
The bell badge number.
Parameters
No parameters.
Request
curl -s "https://api.vendo-ai.com/v1/notifications/unread-count" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"unreadCount": 3
}
}Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
DELETE/v1/notifications/:idDelete a notification
Removes one notification from the caller's list (soft delete).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | Notification id |
Request
curl -s -X DELETE "https://api.vendo-ai.com/v1/notifications/$NOTIFICATION_ID" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"success": true
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | Notification does not exist in this workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
POST/v1/notifications/:id/readMark a notification read
#post-v1-notifications-id-read
Marks one in-app notification as read.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | Notification id |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/notifications/$NOTIFICATION_ID/read" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"success": true
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | Notification does not exist in this workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
CRM integrations 9
GET/v1/integrationsList CRM integrations
External CRM connections of the workspace (Kommo, HubSpot) with status, account, settings and counters. Tokens and secrets are never returned.
Parameters
No parameters.
Request
curl -s "https://api.vendo-ai.com/v1/integrations" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": [
{
"id": "3f9c2a1e-7b4d-4e2a-9c1f-2d8e5a6b7c90",
"provider": "KOMMO",
"status": "ACTIVE",
"account_name": "Ledgerly Sales",
"account_url": "https://ledgerly.kommo.com",
"connected_at": "2026-10-26T09:14:22.000Z",
"last_sync_at": "2026-10-26T09:14:22.000Z",
"has_custom_credentials": true,
"stats": {
"total_synced_conversations": 214,
"total_synced_contacts": 198,
"total_created_leads": 87
},
"settings": {
"sync_conversations": true,
"sync_contacts": true,
"create_leads": true,
"auto_sync": true
}
}
]
}Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
POST/v1/integrations/kommo/credentialsSave Kommo app credentials
#post-v1-integrations-kommo-credentials
Kommo private integration step 1: stores the Integration ID (a UUID) and Secret key of the Vendo app the client registered in their Kommo account. The secret is redacted in logs and never returned. Next: POST /v1/integrations/kommo/connect.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
client_id | body | uuid | yes | Kommo Integration ID |
client_secret | body | string | yes | Kommo Secret key |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/integrations/kommo/credentials" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"client_id": "9c31e135-f366-4178-846d-d56c8b7bfa2c", "client_secret": "••••••••"}'Response
{
"data": {
"saved": true,
"redirect_uri": "https://api.vendo-ai.com/integrations/kommo/callback",
"next": "POST /v1/integrations/kommo/connect"
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | Missing values or client_id is not a UUID |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
GET/v1/integrations/kommo/pipelinesList Kommo pipelines
#get-v1-integrations-kommo-pipelines
Pipelines of the connected Kommo account with their statuses (stages), the values for pipeline_config.
Parameters
No parameters.
Request
curl -s "https://api.vendo-ai.com/v1/integrations/kommo/pipelines" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": [
{
"id": 7712345,
"name": "Sales",
"is_main": true,
"_embedded": {
"statuses": [
{
"id": 61234567,
"name": "Incoming leads",
"sort": 10
}
]
}
}
]
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | Kommo is not connected / not active |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: list_kommo_pipelines
GET/v1/integrations/kommo/usersList Kommo users
#get-v1-integrations-kommo-users
Users of the connected Kommo account, values for pipeline_config.responsible_user_id.
Parameters
No parameters.
Request
curl -s "https://api.vendo-ai.com/v1/integrations/kommo/users" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": [
{
"id": 10987654,
"name": "Sam Carter",
"email": "sam@ledgerly.co.uk"
}
]
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | Kommo is not connected / not active |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: list_kommo_users
GET/v1/integrations/:providerGet one CRM integration
Status and settings of the Kommo or HubSpot connection; { connected: false, provider } when none exists. Poll it after starting OAuth.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
provider | path | string | yes | kommo or hubspot one of: kommo, hubspot |
Request
curl -s "https://api.vendo-ai.com/v1/integrations/$PROVIDER" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"id": "3f9c2a1e-7b4d-4e2a-9c1f-2d8e5a6b7c90",
"provider": "KOMMO",
"status": "ACTIVE",
"connected": true,
"account_name": "Ledgerly Sales",
"account_url": "https://ledgerly.kommo.com",
"has_custom_credentials": true,
"sync_conversations": true,
"sync_contacts": true,
"create_leads": true,
"auto_sync": true,
"pipeline_config": {
"pipeline_id": "7712345",
"stage_id": "61234567",
"responsible_user_id": "10987654"
},
"field_mapping": {},
"last_sync_at": "2026-10-26T09:14:22.000Z",
"total_synced_conversations": 214,
"total_synced_contacts": 198,
"total_created_leads": 87,
"last_error": null,
"last_error_at": null,
"created_at": "2026-10-26T09:14:22.000Z",
"updated_at": "2026-10-26T09:14:22.000Z"
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | Unknown provider |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: get_crm_integration
DELETE/v1/integrations/:providerDisconnect a CRM integration
#delete-v1-integrations-provider
Removes the connection: syncing stops and the stored tokens, credentials and settings are deleted; reconnecting needs OAuth again. Only the person who connected it can disconnect it.
Protected: send {"dry_run": true} to preview, then {"confirm": true} to execute (protocol).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
provider | path | string | yes | kommo or hubspot one of: kommo, hubspot |
dry_run | body | boolean | true = preview only, nothing changes | |
confirm | body | boolean | true = execute. Exactly one of dry_run / confirm is required |
Request
curl -s -X DELETE "https://api.vendo-ai.com/v1/integrations/$PROVIDER" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"dry_run": true}'Response
Show the full response (35 lines)
{
"data": {
"dry_run": true,
"would_do": "Disconnect Kommo (Ledgerly Sales): syncing stops and the stored tokens, credentials and settings are deleted. Reconnecting needs the OAuth flow again. Only the person who connected it can disconnect it.",
"estimated": {
"integration": {
"id": "3f9c2a1e-7b4d-4e2a-9c1f-2d8e5a6b7c90",
"provider": "KOMMO",
"status": "ACTIVE",
"connected": true,
"account_name": "Ledgerly Sales",
"account_url": "https://ledgerly.kommo.com",
"has_custom_credentials": true,
"sync_conversations": true,
"sync_contacts": true,
"create_leads": true,
"auto_sync": true,
"pipeline_config": {
"pipeline_id": "7712345",
"stage_id": "61234567",
"responsible_user_id": "10987654"
},
"field_mapping": {},
"last_sync_at": "2026-10-26T09:14:22.000Z",
"total_synced_conversations": 214,
"total_synced_contacts": 198,
"total_created_leads": 87,
"last_error": null,
"last_error_at": null,
"created_at": "2026-10-26T09:14:22.000Z",
"updated_at": "2026-10-26T09:14:22.000Z"
}
}
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | confirmation_required | Neither dry_run nor confirm was sent |
| 400 | bad_request | Both dry_run and confirm were sent |
| 404 | not_found | The caller has no connection for this provider |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: preview_disconnect_crm_integration, disconnect_crm_integration
POST/v1/integrations/:provider/connectStart OAuth for Kommo / HubSpot
#post-v1-integrations-provider-connect
Returns the provider authorization URL that a human must open in a browser and approve within 20 minutes; the provider callback completes the connection server-side. Kommo requires saved credentials first.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
provider | path | string | yes | kommo or hubspot one of: kommo, hubspot |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/integrations/$PROVIDER/connect" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"url": "https://www.kommo.com/oauth?client_id=9c31e135-…&state=4f1c…&mode=post_message",
"state": "4f1c9a…",
"expires_in_seconds": 1200,
"next": "Open the url in a browser logged in to Kommo and approve, then poll GET /v1/integrations/kommo until status is ACTIVE."
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | Unknown provider or Kommo credentials not configured |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
GET/v1/integrations/:provider/logsCRM integration sync log
#get-v1-integrations-provider-logs
Recent sync events of the caller's own connection (contact/lead created, errors), newest first.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
provider | path | string | yes | kommo or hubspot one of: kommo, hubspot |
limit | query | integer | 1..200, default 50 |
Request
curl -s "https://api.vendo-ai.com/v1/integrations/$PROVIDER/logs" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": [
{
"id": "8a1d4c3b-2e5f-4a6b-9d7c-1e2f3a4b5c6d",
"event_type": "LEAD_CREATED",
"status": "SUCCESS",
"conversation_id": "c2b7e9a4-5d1f-4c8e-a3b6-9f0e1d2c3b4a",
"crm_contact_id": "22918311",
"crm_lead_id": "18822001",
"error_message": null,
"created_at": "2026-10-26T09:14:22.000Z"
}
]
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | The caller has no connection for this provider |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
PATCH/v1/integrations/:provider/settingsUpdate CRM integration settings
#patch-v1-integrations-provider-settings
What is synced and where: sync_conversations, sync_contacts, create_leads, auto_sync, pipeline_config (Kommo pipeline / stage / responsible user) and field_mapping. Changes what Vendo writes into the external CRM.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
provider | path | string | yes | kommo or hubspot one of: kommo, hubspot |
sync_conversations | body | boolean | ||
sync_contacts | body | boolean | ||
create_leads | body | boolean | ||
auto_sync | body | boolean | ||
pipeline_config | body | object | { pipeline_id, stage_id, responsible_user_id } | |
field_mapping | body | object | string → string map |
Request
curl -s -X PATCH "https://api.vendo-ai.com/v1/integrations/$PROVIDER/settings" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"pipeline_config": {
"pipeline_id": "7712345",
"stage_id": "61234567",
"responsible_user_id": "10987654"
},
"create_leads": true
}'Response
{
"data": {
"id": "3f9c2a1e-7b4d-4e2a-9c1f-2d8e5a6b7c90",
"provider": "KOMMO",
"status": "ACTIVE",
"connected": true,
"account_name": "Ledgerly Sales",
"account_url": "https://ledgerly.kommo.com",
"has_custom_credentials": true,
"sync_conversations": true,
"sync_contacts": true,
"create_leads": true,
"auto_sync": true,
"pipeline_config": {
"pipeline_id": "7712345",
"stage_id": "61234567",
"responsible_user_id": "10987654"
},
"field_mapping": {},
"last_sync_at": "2026-10-26T09:14:22.000Z",
"total_synced_conversations": 214,
"total_synced_contacts": 198,
"total_created_leads": 87,
"last_error": null,
"last_error_at": null,
"created_at": "2026-10-26T09:14:22.000Z",
"updated_at": "2026-10-26T09:14:22.000Z"
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | Wrong types or nothing to update |
| 404 | not_found | No connection for this provider |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
Referral program 5
GET/v1/referralReferral overview
Program terms (commission %, minimum payout, link base), totals (visits, signups, paying customers, revenue, earned, paid out, pending, available), every link with stats, and payout request statuses (amounts and status only). Payout requests can only be made in the app.
Parameters
No parameters.
Request
curl -s "https://api.vendo-ai.com/v1/referral" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Show the full response (36 lines)
{
"data": {
"program": {
"commission_pct": 20,
"min_payout_usd": 50,
"link_base": "https://vendo-ai.com/?ref="
},
"totals": {
"visits": 412,
"signups": 9,
"paying_customers": 2,
"revenue_usd": 1180,
"earned_usd": 236,
"paid_out_usd": 0,
"pending_usd": 0,
"available_usd": 236
},
"links": [
{
"id": "8a1d4c3b-2e5f-4a6b-9d7c-1e2f3a4b5c6d",
"code": "K7Q2XH",
"label": "LinkedIn post",
"url": "https://vendo-ai.com/?ref=K7Q2XH",
"created_at": "2026-10-26T09:14:22.000Z",
"archived_at": null,
"visits": 412,
"signups": 9,
"paying_customers": 2,
"revenue_usd": 1180,
"earned_usd": 236
}
],
"payouts": [],
"referred_users_count": 9
}
}Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
POST/v1/referral/linksCreate a referral link
Creates a tracked referral link (up to 20 active links).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
label | body | string | ≤80 chars, where you will share it |
Request
curl -s -X POST "https://api.vendo-ai.com/v1/referral/links" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"label": "Newsletter"}'Response
{
"data": {
"id": "8a1d4c3b-2e5f-4a6b-9d7c-1e2f3a4b5c6d",
"code": "P3M9TA",
"label": "Newsletter",
"url": "https://vendo-ai.com/?ref=P3M9TA",
"created_at": "2026-10-26T09:14:22.000Z",
"archived_at": null,
"visits": 0,
"signups": 0,
"paying_customers": 0,
"revenue_usd": 0,
"earned_usd": 0
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | Label too long or 20 active links already |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: create_referral_link
PATCH/v1/referral/links/:idRename / archive a referral link
Changes the label or archives the link (archived links stop attributing new signups; archived=false restores it).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
id | path | uuid | yes | Link id |
label | body | string | ≤80 chars | |
archived | body | boolean |
Request
curl -s -X PATCH "https://api.vendo-ai.com/v1/referral/links/$LINK_ID" \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"archived": true}'Response
{
"data": {
"id": "8a1d4c3b-2e5f-4a6b-9d7c-1e2f3a4b5c6d",
"code": "K7Q2XH",
"label": "LinkedIn post",
"url": "https://vendo-ai.com/?ref=K7Q2XH",
"created_at": "2026-10-26T09:14:22.000Z",
"archived_at": "2026-10-26T09:14:22.000Z",
"visits": 412,
"signups": 9,
"paying_customers": 2,
"revenue_usd": 1180,
"earned_usd": 236
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 404 | not_found | Link does not exist in this workspace |
| 400 | invalid_request | Nothing to update |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: update_referral_link
GET/v1/referral/referred-usersReferred users
#get-v1-referral-referred-users
People who signed up through the caller's links: masked email, link, signup and first-payment dates, revenue, commission and free/paying status.
Parameters
No parameters.
Request
curl -s "https://api.vendo-ai.com/v1/referral/referred-users" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"referred_users": [
{
"id": "c2b7e9a4-5d1f-4c8e-a3b6-9f0e1d2c3b4a",
"link_id": "8a1d4c3b-2e5f-4a6b-9d7c-1e2f3a4b5c6d",
"link_code": "K7Q2XH",
"email_masked": "j***@brightpay.io",
"signed_up_at": "2026-10-26T09:14:22.000Z",
"first_payment_at": null,
"revenue_usd": 0,
"earned_usd": 0,
"status": "free"
}
],
"total": 9
}
}Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: list_referred_users
Analytics & planner API 4
Cross-product analytics and the planner calendar.
Analytics 3
GET/v1/analytics/agentsAgents analytics page
Every metric of the /analytics/agents page for a UTC date range, optionally one agent: totals, rates, daily series, funnel (widget loads → opens → conversations → contacts → leads), by agent and channel, leads by source, recent leads, credits by type, languages, pages, traffic sources, hours, durations and the previous period.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
from | query | date | YYYY-MM-DD | |
to | query | date | YYYY-MM-DD | |
agent_id | query | uuid | Narrow to one agent |
Request
curl -s "https://api.vendo-ai.com/v1/analytics/agents" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Show the full response (62 lines)
{
"data": {
"range": {
"from": "2026-10-01",
"to": "2026-10-26",
"days": [
"2026-10-01",
"…"
]
},
"previous": {
"totals": {
"conversations": 141,
"leads": 22
}
},
"agents": [
{
"id": "3f9c2a1e-7b4d-4e2a-9c1f-2d8e5a6b7c90",
"name": "Ava — Ledgerly",
"agent_type": "widget",
"status": "active"
}
],
"totals": {
"conversations": 168,
"contacts": 61,
"leads": 27,
"voice_calls": 12,
"chats": 156,
"credits": 912
},
"rates": {
"contact_rate": 0.36,
"lead_rate": 0.16
},
"daily": {
"conversations": [
4,
7,
6
]
},
"funnel": {
"widget_loads": 18240,
"widget_opens": 1310,
"conversations": 168,
"contacts": 61,
"leads": 27
},
"by_agent": [],
"by_channel": [],
"leads_by_source": [],
"recent_leads": [],
"credits_by_type": [],
"languages": [],
"pages": [],
"traffic_sources": [],
"hours": [],
"durations": []
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | Bad date format or from > to |
| 404 | not_found | Agent does not exist in this workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: get_analytics_agents
GET/v1/analytics/campaignsSales Process analytics
Workspace KPIs for classic campaigns (Analytics page): totals, open/click/reply rates and per-campaign performance, optionally for a date range. For SDR + campaigns combined use GET /v1/analytics/sdr.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
from | query | date | YYYY-MM-DD | |
to | query | date | YYYY-MM-DD |
Request
curl -s "https://api.vendo-ai.com/v1/analytics/campaigns" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"summary": {
"totalCampaigns": 6,
"activeCampaigns": 2,
"totalEmailsSent": 4820,
"totalOpened": 1902,
"totalClicked": 133,
"totalReplies": 121,
"totalContacts": 2210,
"openRate": 39.5,
"clickRate": 2.8,
"replyRate": 2.5
},
"campaignPerformance": [
{
"id": "c3a7e2f4-1b5d-4e8a-9c0f-6d2b8a4e7f15",
"name": "UK SaaS founders — Q4",
"status": "active",
"emailsSent": 1260,
"opened": 512,
"replied": 37,
"openRate": 40.6,
"replyRate": 2.9
}
]
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | A parameter has the wrong type, range or enum value. |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
GET/v1/analytics/sdrSDR & outreach analytics page
Everything on the /analytics/sdr page for a UTC date range: Vendo SDR and classic campaigns together, or one SDR search (search_id) or one campaign (campaign_id), never both. Includes delivery and inbox placement, funnel, by product / source / type, enrichment and per-process rows. (The SDR domain also exposes a similar GET /v1/sdr/analytics.)
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
from | query | date | YYYY-MM-DD | |
to | query | date | YYYY-MM-DD | |
search_id | query | uuid | One SDR search | |
campaign_id | query | uuid | One campaign |
Request
curl -s "https://api.vendo-ai.com/v1/analytics/sdr" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Show the full response (56 lines)
{
"data": {
"workspace_id": "8a1d4c3b-2e5f-4a6b-9d7c-1e2f3a4b5c6d",
"search_id": null,
"campaign_id": null,
"range": {
"from": "2026-10-01",
"to": "2026-10-26",
"days": [
"2026-10-01",
"…"
]
},
"totals": {
"signals": 1840,
"contacts": 622,
"sent": 1904,
"replies": 71
},
"rates": {
"reply_rate": 0.037
},
"delivery": {
"sent": 1904,
"delivered": 1861,
"bounced": 43,
"inbox_placement": {
"inbox": 0.91,
"spam": 0.09
}
},
"by_product": {
"sdr": {
"sent": 1210
},
"classic": {
"sent": 694
}
},
"by_source": [],
"by_type": [],
"contacts_by_source": [],
"enrichment": {},
"notifications_by_type": [],
"processes": [
{
"kind": "sdr",
"id": "c2b7e9a4-5d1f-4c8e-a3b6-9f0e1d2c3b4a",
"name": "UK fintech funding",
"sent": 640,
"replies": 29
}
],
"funnel": {}
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | Both ids given, bad date format or from > to |
| 404 | not_found | search_id / campaign_id not in this workspace |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: get_analytics_sdr
Planner 1
GET/v1/planner/eventsPlanner sending windows
The planner layer of the calendar page: per day and channel sending windows of every campaign and SDR search (paused flag, planned today, sent today) with campaign / search info. Wide ranges are materialised only up to a bounded window, range says what was returned.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
from | query | datetime | yes | ISO date/time |
to | query | datetime | yes | ISO date/time |
Request
curl -s "https://api.vendo-ai.com/v1/planner/events?from=$FROM&to=$TO" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Show the full response (36 lines)
{
"data": {
"events": [
{
"id": "campaign:3f9c2a1e-7b4d-4e2a-9c1f-2d8e5a6b7c90:2026-10-27",
"type": "campaign_window",
"start": "2026-10-27T09:00:00.000Z",
"end": "2026-10-27T17:00:00.000Z",
"timezone": "Europe/London",
"all_day": false,
"channels": [
{
"channel": "email",
"paused": false,
"planned_today": 120,
"sent_today": 0
}
],
"campaign": {
"id": "3f9c2a1e-7b4d-4e2a-9c1f-2d8e5a6b7c90",
"name": "Q4 CFO outreach",
"status": "active",
"agent_id": "c2b7e9a4-5d1f-4c8e-a3b6-9f0e1d2c3b4a",
"daily_limit": 120,
"color": "#2563eb",
"kind": "classic"
}
}
],
"range": {
"from": "2026-10-27T00:00:00.000Z",
"to": "2026-11-03T00:00:00.000Z",
"truncated": false
}
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | from/to missing, not ISO, or from > to |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: get_planner_events
Billing (read-only) API 14
Balance, subscription, plan limits, pricing, transactions, invoices and credit usage. Read-only by design.
Billing 14
GET/v1/billing/balanceCredit balance
Read-only. FIX: credits are shared per workspace and owned by the workspace owner, the gateway now resolves the owner first (fail-closed), so member keys see the workspace numbers and a read can never create a free-trial balance row for the member.
Parameters
No parameters.
Request
curl -s "https://api.vendo-ai.com/v1/billing/balance" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"balance": 2140
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 502 | internal_error | Workspace owner could not be resolved |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: get_balance
GET/v1/billing/balance/fullBalance + plan summary + spent totals
balance + plan label + workspace owner. Maps to vendo-billing's GET /api/billing/credits/balance which already resolves workspace owner and returns the richer body. Richer than get_balance: returns balance, total subscription credits, total purchased, total spent, free-trial credits, email/workspace info.
Parameters
No parameters.
Request
curl -s "https://api.vendo-ai.com/v1/billing/balance/full" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: get_balance_full
GET/v1/billing/currenciesSupported currencies
Currencies Vendo bills in (code, symbol, name) plus the account's own billing currency from the current subscription. Read-only.
Parameters
No parameters.
Request
curl -s "https://api.vendo-ai.com/v1/billing/currencies" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"total": 3,
"currencies": [
{
"code": "USD",
"symbol": "$",
"name": "US Dollar"
},
{
"code": "GBP",
"symbol": "£",
"name": "British Pound"
},
{
"code": "EUR",
"symbol": "€",
"name": "Euro"
}
],
"account_currency": "GBP"
}
}Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
GET/v1/billing/invoicesStripe invoices (paid + open)
Stripe invoices (paid + open). Optional ?year=YYYY&month=MM to scope. List of Stripe invoices (paid + open). Optional year/month scope.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
year | query | integer | ||
month | query | integer | min 1; max 12 |
Request
curl -s "https://api.vendo-ai.com/v1/billing/invoices" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: get_invoices
GET/v1/billing/pricingPricing catalogue
Public price list: subscription plans (type SUBSCRIPTION, name e.g. "Starter"), credit top-ups and per-operation costs (operation_type, cost_in_credits), with currency, price, included credits and max_agents (null = unlimited). Optional ?currency=USD|GBP|EUR returns one currency. Fixed (2026-10-26): rows no longer expose the internal stripe_price_id_encrypted; the plan name is the name field (there is no plan field).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
currency | query | string | one of: USD, GBP, EUR |
Request
curl -s "https://api.vendo-ai.com/v1/billing/pricing" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Show the full response (40 lines)
{
"data": [
{
"id": "3f9c2a1e-7b4d-4e2a-9c1f-2d8e5a6b7c90",
"name": "Starter",
"description": "For one sales agent",
"type": "SUBSCRIPTION",
"currency": "GBP",
"price": 49,
"credits": 1000,
"operation_type": null,
"cost_in_credits": null,
"max_agents": 1,
"is_active": true,
"is_public": true,
"metadata": {
"tier": 1
},
"created_at": "2026-06-05T09:00:00.000Z",
"updated_at": "2026-10-01T09:00:00.000Z"
},
{
"id": "8b1e4c7a-2d5f-4a9b-9c3e-6f0a1d2b3c4e",
"name": "Text generation",
"description": null,
"type": "OPERATION_COST",
"currency": "GBP",
"price": 0,
"credits": 0,
"operation_type": "TEXT_GENERATION",
"cost_in_credits": 1,
"max_agents": null,
"is_active": true,
"is_public": true,
"metadata": null,
"created_at": "2026-06-05T09:00:00.000Z",
"updated_at": "2026-06-05T09:00:00.000Z"
}
]
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | Unsupported currency |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: get_pricing
GET/v1/billing/sdr/email-capacitySDR sender capacity (included + extras, daily limit)
#get-v1-billing-sdr-email-capacity
Vendo SDR managed sender entitlement (included addresses, purchased extras, estimated daily emails). How many SDR sender email addresses the account has (included + purchased extras) and the estimated daily email throughput. Read-only.
Parameters
No parameters.
Request
curl -s "https://api.vendo-ai.com/v1/billing/sdr/email-capacity" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
GET/v1/billing/subscriptionCurrent subscription (plan, status, dates)
current plan, status, period_end, cancel_at. Plan, status (ACTIVE/CANCELLED/PAST_DUE/EXPIRED/TRIALING), period dates, currency, amount, Stripe IDs.
Parameters
No parameters.
Request
curl -s "https://api.vendo-ai.com/v1/billing/subscription" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP: get_subscription
GET/v1/billing/subscription/limitsPlan limits
#get-v1-billing-subscription-limits
Read-only. FIX: credits are shared per workspace and owned by the workspace owner, the gateway now resolves the owner first (fail-closed), so member keys see the workspace numbers and a read can never create a free-trial balance row for the member.
Parameters
No parameters.
Request
curl -s "https://api.vendo-ai.com/v1/billing/subscription/limits" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"maxAgents": 5,
"baseMaxAgents": 3,
"extraAgentSlots": 2,
"plan": "SCALE",
"status": "ACTIVE",
"isLegacy": false,
"currency": "GBP"
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 502 | internal_error | Workspace owner could not be resolved |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
GET/v1/billing/transactionsCredit transactions
Workspace credit transactions, newest first. type / from / to filter (vendo-billing pages by limit/offset only, so the gateway scans newest → oldest until it passes from, at most 5000 rows). Fixed (2026-10-26): the scan stopped after the first page of matches, so filtered reads always returned total_matched: null and a hard-coded note. Now total_matched is exact whenever the scan reaches the start of the range (or the oldest row); only when 5000 rows are not enough is it null, with a note giving the real number of rows scanned. Without filters it is a plain page.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
type | query | string | Transaction type, e.g. EMAIL_OUTREACH, SDR_CONTACT, PURCHASE, SUBSCRIPTION_GRANT | |
from | query | date | ISO | |
to | query | date | ISO; date-only is inclusive | |
limit | query | integer | 1..200, default 50 | |
offset | query | integer |
Request
curl -s "https://api.vendo-ai.com/v1/billing/transactions" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"transactions": [
{
"id": "3f9c2a1e-7b4d-4e2a-9c1f-2d8e5a6b7c90",
"user_id": "5e6f7a8b-9c0d-4e1f-8a2b-3c4d5e6f7a8b",
"type": "SDR_CONTACT",
"amount": -2,
"balance_after": 2140,
"description": "SDR contact for search UK fintech funding",
"agent_id": null,
"created_at": "2026-10-26T09:14:22.000Z"
}
],
"total_matched": 1,
"has_more": false,
"limit": 50,
"offset": 0,
"filters": {
"type": "SDR_CONTACT",
"from": "2026-10-01",
"to": "2026-10-26"
},
"scanned": 412
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | from/to not ISO or from > to |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
GET/v1/billing/usage/agents/:agentIdSpend for one agent
#get-v1-billing-usage-agents-agentid
credits spent by one agent. Total credits spent by one specific agent.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
agentId | path | string | yes |
Request
curl -s "https://api.vendo-ai.com/v1/billing/usage/agents/$AGENT_ID" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
GET/v1/billing/usage/agents/:agentId/connectionsPer-channel spend for one agent
#get-v1-billing-usage-agents-agentid-connections
spend by connection. Per-channel credit spend (widget, telegram, phone, etc.) for one agent. Identifies the costliest channels.
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
agentId | path | string | yes |
Request
curl -s "https://api.vendo-ai.com/v1/billing/usage/agents/$AGENT_ID/connections" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Returns { "data": … } with the resource(s) described above.
Errors
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
GET/v1/billing/usage/by-agentSpend per agent
#get-v1-billing-usage-by-agent
Read-only. FIX: credits are shared per workspace and owned by the workspace owner, the gateway now resolves the owner first (fail-closed), so member keys see the workspace numbers and a read can never create a free-trial balance row for the member.
Parameters
No parameters.
Request
curl -s "https://api.vendo-ai.com/v1/billing/usage/by-agent" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"userId": "3f9c2a1e-7b4d-4e2a-9c1f-2d8e5a6b7c90",
"agents": [
{
"agentId": "c2b7e9a4-5d1f-4c8e-a3b6-9f0e1d2c3b4a",
"creditsSpent": 912
}
]
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 502 | internal_error | Workspace owner could not be resolved |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
GET/v1/billing/usage/by-typeSpend sessions by type
Per-session credit spend (newest first) with connectionType and agent. from/to filter (scan of the newest rows, up to 5000) and add totals_by_type for the range. Fixed (2026-10-26): totals_by_type is always present and total_matched is exact when the scan reaches from; totals_complete says whether the totals cover the whole range (false only when 5000 rows were not enough, the note then gives the real number scanned).
Parameters
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
from | query | date | ISO | |
to | query | date | ISO; date-only is inclusive | |
limit | query | integer | 1..500, default 50 | |
offset | query | integer |
Request
curl -s "https://api.vendo-ai.com/v1/billing/usage/by-type" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
Show the full response (33 lines)
{
"data": {
"sessions": [
{
"id": "3f9c2a1e-7b4d-4e2a-9c1f-2d8e5a6b7c90",
"date": "2026-10-26T09:14:22.000Z",
"agentId": "c2b7e9a4-5d1f-4c8e-a3b6-9f0e1d2c3b4a",
"agentName": "Ava — Ledgerly",
"agentType": "widget",
"connectionType": "widget",
"creditsSpent": 1,
"description": "Text message",
"metadata": null
}
],
"total_matched": 1,
"has_more": false,
"limit": 50,
"offset": 0,
"totals_by_type": {
"widget": {
"credits": 1,
"count": 1
}
},
"filters": {
"from": "2026-10-26",
"to": "2026-10-26"
},
"scanned": 37,
"totals_complete": true
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | from/to not ISO or from > to |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
GET/v1/billing/usage/monthlyMonthly spend breakdown
Read-only. FIX: credits are shared per workspace and owned by the workspace owner, the gateway now resolves the owner first (fail-closed), so member keys see the workspace numbers and a read can never create a free-trial balance row for the member.
Parameters
No parameters.
Request
curl -s "https://api.vendo-ai.com/v1/billing/usage/monthly" \
-H "Authorization: Bearer $VENDO_API_KEY"Response
{
"data": {
"month": "2026-10",
"totalSpent": 1860,
"breakdown": [
{
"type": "EMAIL_OUTREACH",
"amount": 1120,
"count": 1120
}
]
}
}Errors
| HTTP | Code | When |
|---|---|---|
| 502 | internal_error | Workspace owner could not be resolved |
Plus the common errors (401 missing_api_key/bad_api_key, 403 missing_scope, 404 not_found for ids outside your workspace, 429 rate_limited).
MCP server
The Model Context Protocol lets AI assistants call external tools. The Vendo AI MCP server exposes the whole /v1 API as 396 tools, so Claude, ChatGPT, Cursor, VS Code, Windsurf or your own agent can create agents, run Vendo SDR, launch campaigns and work the CRM in plain English.
- Endpoint:
https://api.vendo-ai.com/mcp: Streamable HTTP (POST JSON-RPC, GET for the server event stream, DELETE to close; the server returns anmcp-session-idheader to send on later requests). - Auth: the same API key, as
Authorization: Bearer vnd_live_…(orX-API-Key) on the MCP connection. There is no OAuth flow; clients that cannot send a header can use themcp-remotebridge shown below. - Same rules as REST: each tool calls the documented REST route with your key, same scopes, rate limits, credits, audit log and workspace isolation. The MCP server holds no secrets of its own.
- Without a key the server allows
initializeandtools/list(for registries and discovery); tool calls then fail with401. - Errors are tool results: a failed call returns
isError: truewith{"error": true, "code", "message", "status"}over HTTP 200. Rate limiting readsrate_limited, retry after N s(plusretry_after_seconds), retry the same tool after N seconds.
MCP setup
Claude Code
claude mcp add --transport http vendo-ai https://api.vendo-ai.com/mcp \
--header "Authorization: Bearer vnd_live_YOUR_KEY"
# check it
claude mcp listClaude Desktop
Add to claude_desktop_config.json (Settings › Developer › Edit Config) and restart. The mcp-remote bridge (needs Node.js) forwards the Authorization header to the remote server.
{
"mcpServers": {
"vendo-ai": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
"https://api.vendo-ai.com/mcp",
"--header",
"Authorization:${VENDO_AUTH}"
],
"env": {
"VENDO_AUTH": "Bearer vnd_live_YOUR_KEY"
}
}
}
}Cursor
{
"mcpServers": {
"vendo-ai": {
"url": "https://api.vendo-ai.com/mcp",
"headers": {
"Authorization": "Bearer vnd_live_YOUR_KEY"
}
}
}
}VS Code (GitHub Copilot agent mode)
{
"inputs": [
{
"type": "promptString",
"id": "vendo-key",
"description": "Vendo AI API key",
"password": true
}
],
"servers": {
"vendo-ai": {
"type": "http",
"url": "https://api.vendo-ai.com/mcp",
"headers": {
"Authorization": "Bearer ${input:vendo-key}"
}
}
}
}Windsurf
{
"mcpServers": {
"vendo-ai": {
"serverUrl": "https://api.vendo-ai.com/mcp",
"headers": {
"Authorization": "Bearer vnd_live_YOUR_KEY"
}
}
}
}ChatGPT and the OpenAI API
ChatGPT connectors that let you set a custom Authorization header can use the URL and header above. With the OpenAI Responses API, pass the header on the MCP tool:
# OpenAI Responses API — remote MCP tool
from openai import OpenAI
client = OpenAI()
resp = client.responses.create(
model="gpt-5",
tools=[{
"type": "mcp",
"server_label": "vendo-ai",
"server_url": "https://api.vendo-ai.com/mcp",
"headers": {"Authorization": "Bearer vnd_live_YOUR_KEY"},
"require_approval": "always",
}],
input="List my Vendo AI agents and their status.",
)
print(resp.output_text)Claude API (MCP connector)
# Claude Messages API — MCP connector (beta)
curl https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: mcp-client-2025-11-20" \
-H "content-type: application/json" \
-d '{
"model": "claude-opus-5-5",
"max_tokens": 4096,
"mcp_servers": [{
"type": "url",
"url": "https://api.vendo-ai.com/mcp",
"name": "vendo-ai",
"authorization_token": "vnd_live_YOUR_KEY"
}],
"tools": [{"type": "mcp_toolset", "mcp_server_name": "vendo-ai"}],
"messages": [{"role": "user", "content": "Show my Vendo AI credit balance and active campaigns."}]
}'Any MCP client / raw HTTP
# Raw Streamable HTTP: initialize, then list tools
curl -s -D - https://api.vendo-ai.com/mcp \
-H "Authorization: Bearer $VENDO_API_KEY" \
-H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"1"}}}'
# copy the mcp-session-id response header, then:
curl -s https://api.vendo-ai.com/mcp \
-H "Authorization: Bearer $VENDO_API_KEY" -H "mcp-session-id: $SESSION" \
-H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}'Safety model: preview / execute tool pairs
MCP has no dry_run flag. Instead, each protected action is split into two tools: the preview tool runs dry_run and returns what would happen; the execute tool runs confirm. Tool descriptions instruct the model to call the execute tool only after the user has seen the preview, and because the tool name carries the intent, a model cannot launch or delete by accident. Destructive tools that act on a single call (such as sending a reply) say so in their description. Configure your client to ask for approval on execute tools if you want a human in the loop for every call.
All 50 preview → execute pairs
MCP tool reference 396
All 396 tools of the Vendo AI MCP server, grouped like the REST reference. Each card lists the tool's arguments (generated from its JSON Schema, Req. marks required arguments; nested fields use dotted names) and the REST route(s) it calls, so every tool behaves exactly like the documented endpoint, with the same scopes, limits and errors.
Agents tools 100
Agent core & lifecycle 14
create_agentCreate a new agentupdated
Create an AI agent like step 1 of the app's builder. Choose its direction: purpose "sales" (default), "support" or "info". The agent starts in DRAFT and contacts nobody. Needs at least 1 credit on the balance (checked, not charged). Fails with a plan-limit error when all agent slots are used (see get_agent_plan_limits). Next: set_agent_company → generate_agent_script → channels.
| Argument | Type | Req. | Description |
|---|---|---|---|
name | string | yes | Agent's display name, e.g. 'Anna from Acme' |
description | string | 1-2 sentences about what the agent does | |
purpose | string | sales = sales agent, support = customer support, info = information assistant one of: sales, support, info | |
gender | string | Voice/face gender (default female) one of: male, female | |
avatar_preset | string | Preset face avatar-1 … avatar-20 (male: 1,4,6,8,10-15; female: 2,3,5,7,9,16-20). For your own photo use set_agent_avatar_from_url afterwards pattern ^avatar-([1-9]|1[0-9]|20)$ |
Calls: POST /v1/agents
delete_agentDelete an agent (irreversible)
Permanently delete an agent. NOT reversible. Only call after the user has reviewed preview_delete_agent output and explicitly approved.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes |
Calls: DELETE /v1/agents/:id
duplicate_agentDuplicate an agent
Create a copy of an existing agent (new id, same config). Counts toward agent capacity.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes |
get_agentGet an agent
Fetch the full details of a single agent by id.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | The agent UUID |
Calls: GET /v1/agents/:id
get_agent_plan_limitsGet agent capacity
How many agents can the user create (base plan + purchased extra slots) vs how many they have. Read-only.
No arguments.
Calls: GET /v1/agents/plan-limits
list_agentsList agents
preview_delete_agentPreview deleting an agentpreview
Show what would happen if you delete an agent. The agent is NOT deleted. ALWAYS call this first and surface the result to the user before calling delete_agent.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes |
Calls: DELETE /v1/agents/:id
set_agent_avatar_from_urlSet the agent's own photoupdated
Upload a custom avatar photo (JPG, PNG or GIF, ≤5 MB) from a public image URL or base64. Replaces the preset face. Free.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Agent id (UUID), from list_agents |
image_url | string | Public https URL of the image | |
image_base64 | string | Base64 or data: URL of the image (use instead of image_url) |
Calls: PATCH /v1/agents/:id/avatar
set_agent_companySet company info (name / website / description)
Required before generate_agent_script will work. Set the company the agent represents, its name, website, and what it sells.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | |
company_name | string | yes | |
company_website | string | yes | |
company_description | string | yes |
Calls: PATCH /v1/agents/:id/company
set_agent_contact_channelsSet the contacts the agent hands outupdated
Set the meeting link, presentation link, email, phone, WhatsApp, Telegram and up to 10 custom links that the agent gives to visitors (also shown as screen cards). MERGE: only the channels you pass change; pass "" (or null for custom_links) to clear one. Write, free.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Agent id (UUID), from list_agents |
meeting_url | string | Booking link, e.g. Calendly (http/https) | |
presentation_url | string | Link to a presentation/deck (http/https) | |
email | string | ||
phone | string | ||
whatsapp | string | ||
telegram | string | ||
custom_links | object[] | null | Replaces the whole custom link list; null or [] clears it max items 10 | |
custom_links[].label | string | yes | max length 60 |
custom_links[].url | string | yes | |
custom_links[].note | string | When the agent should share it max length 200 |
set_agent_integrationsSet legacy integration flagsnew
Legacy flags on the agent (zoom_enabled, google_meet_enabled, website_embed_enabled). Channels are managed with the connection tools; this does not connect anything. Free.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Agent id (UUID), from list_agents |
zoom_enabled | boolean | ||
google_meet_enabled | boolean | ||
website_embed_enabled | boolean |
set_agent_statusPause / resume / archive an agent
Change agent status. Allowed values: ACTIVE, PAUSED, ARCHIVED, DRAFT.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | |
status | string | yes | one of: ACTIVE, PAUSED, ARCHIVED, DRAFT |
Calls: PATCH /v1/agents/:id/status
update_agentUpdate agent basicsupdated
Change an agent's name, description, direction (purpose), voice gender or preset face. Only the fields you pass change. Company name/website/description → set_agent_company; contacts → set_agent_contact_channels. Free.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Agent id (UUID), from list_agents |
name | string | ||
description | string | ||
purpose | string | one of: sales, support, info | |
gender | string | one of: male, female | |
avatar_preset | string | Preset face avatar-1 … avatar-20 pattern ^avatar-([1-9]|1[0-9]|20)$ |
Calls: PATCH /v1/agents/:id
update_agent_scriptEdit the agent's script stagesnew
Replace the script stages (rename, reorder, edit, add or remove stages) and optionally the tone, like the builder's script editor. Send the FULL list of stages you want; the full text is rebuilt from them. Get the current stages from get_agent (script_sections). Free.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Agent id (UUID), from list_agents |
sections | object[] | yes | All stages in order min items 1 |
sections[].id | string | ||
sections[].title | string | yes | |
sections[].content | string | yes | |
sections[].icon | string | ||
script_style | string | Tone; omit to keep the current one one of: PROFESSIONAL_FORMAL, FRIENDLY_CASUAL, TECHNICAL_EXPERT, CONSULTATIVE | |
email_script | string | Written-channel script (email/LinkedIn); omit to keep |
Calls: PATCH /v1/agents/:id/script
Builder: website scan, description, script 10
deep_scan_agentDeep-scan the company website (15 credits)updatedexecute
Read the company website + LinkedIn and extract description, team, employee count, contacts, social links and pricing. Costs 15 credits, takes 60-180 s. With persist=true (default) the result is SAVED like in the app: company_description replaced, team/contacts/social/pricing stored (creates company + people records, fills blank contact channels). Waits up to ~200 s; if still running it returns a job_id, then call get_agent_job. Only call after preview_deep_scan and the user's approval.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Agent id (UUID), from list_agents |
company_name | string | Defaults to the agent's company_name | |
company_website | string | Defaults to the agent's company_website | |
persist | boolean | Save the result onto the agent (default true) | |
auto_update | object | Re-scan automatically | |
auto_update.enabled | boolean | yes | |
auto_update.interval | string | yes | one of: daily, weekly, biweekly, monthly, quarterly |
generate_agent_descriptionDraft the company description from the websiteupdated
AI-write "what the agent knows" about the company from its website (no full scan). Costs 1 credit. NOT saved, pass the text to set_agent_company (company_description).
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Agent id (UUID), from list_agents |
company_name | string | Defaults to the agent's company_name | |
company_website | string | Defaults to the agent's company_website | |
target_language | string | Write in this language, e.g. "Ukrainian" |
generate_agent_page_descriptionsDescribe website pages with AInew
#generate_agent_page_descriptions
Write AI descriptions + keywords for website pages (output of scan_agent_website). Costs 1 credit per 5 pages. Not saved.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Agent id (UUID), from list_agents |
pages | object[] | yes | min items 1 |
pages[].path | string | yes | |
pages[].title | string | yes |
generate_agent_scriptGenerate the agent's scriptupdated
Generate the agent's script (stages + full text + email/LinkedIn script) in the chosen tone for its direction (sales / support / info), using the company description and uploaded files. Saves it on the agent (overwrites). Costs 1 credit, takes 60-90 s. Needs company_name and company_description (set_agent_company). If it is still running after ~200 s you get a job_id → get_agent_job.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Agent id (UUID), from list_agents |
script_style | string | yes | Tone one of: PROFESSIONAL_FORMAL, FRIENDLY_CASUAL, TECHNICAL_EXPERT, CONSULTATIVE |
purpose | string | Defaults to the agent's direction one of: sales, support, info |
generate_support_scriptGenerate a support knowledge script (legacy)new
Legacy support-script generator of the old text-widget creators. Prefer generate_agent_script with purpose "support". Needs company name + description. Costs 1 credit.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Agent id (UUID), from list_agents |
preview_deep_scanPreview a deep website scanupdatedpreview
Show what deep_scan_agent would do: cost (15 credits), duration (60-180 s) and what gets saved. Nothing runs. ALWAYS call first and get the user's approval.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Agent id (UUID), from list_agents |
company_name | string | ||
company_website | string | ||
persist | boolean |
save_scan_dataSave scan facts onto the agentupdated
Save company facts (team, contacts, social links, pricing, employee count, auto re-scan) onto the agent, what the app does after a scan. Side effects: stores them in the agent, creates/updates the company and people records, fills BLANK contact channels (never overwrites manual ones). Not needed after deep_scan_agent with persist=true. Free.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Agent id (UUID), from list_agents |
company_name | string | yes | |
company_website | string | yes | |
directors | object[] | Team / management | |
directors[].name | string | yes | |
directors[].position | string | yes | |
directors[].email | string | ||
directors[].phone | string | ||
directors[].linkedin | string | ||
contacts | object | ||
contacts.addresses | string[] | ||
contacts.phones | string[] | ||
contacts.emails | string[] | ||
social_links | object | ||
social_links.linkedin | string | ||
social_links.facebook | string | ||
social_links.instagram | string | ||
social_links.twitter | string | ||
social_links.whatsapp | string | ||
social_links.telegram | string | ||
social_links.calendly | string | ||
social_links.booking | string | ||
pricing | string | ||
employee_count | number | string | ||
auto_update | object | ||
auto_update.enabled | boolean | yes | |
auto_update.interval | string | yes | one of: daily, weekly, biweekly, monthly, quarterly |
scan_agent_websiteScan the agent's website structurenew
List the pages of a website (path, title, description, keywords) for this agent, used by the page-navigation feature. Costs 2 credits. Read-only for the agent (nothing saved).
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Agent id (UUID), from list_agents |
website_url | string | yes |
scan_website_quickQuick website scan
Crawl a website's structure (sitemap, top pages) BEFORE creating an agent. Useful when deciding which company to set on the agent. Costs ~2 credits.
| Argument | Type | Req. | Description |
|---|---|---|---|
website_url | string | yes |
Calls: POST /v1/agents/scan
test_agent_replyAsk the agent a test question (text)new
Send a message to the agent and get its reply using its current script, a text test, nobody is contacted. Needs a generated script. Use history for a multi-turn test.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Agent id (UUID), from list_agents |
message | string | yes | What the visitor says |
history | object[] | Earlier turns, oldest first | |
history[].role | string | yes | one of: user, assistant |
history[].content | string | yes |
Long-running jobs 2
get_agent_jobGet a long job (deep scan / script) resultnew
Status and result of a deep scan or script generation job started by deep_scan_agent / generate_agent_script (kept 1 hour). Poll every 10-20 s while status is "running". Read-only.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Agent id (UUID), from list_agents |
job_id | string | yes |
list_agent_jobsList recent long jobs of an agentnew
Deep scans and script generations of this agent in the last hour (status only), use when a job_id was lost. Read-only.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Agent id (UUID), from list_agents |
Calls: GET /v1/agents/:id/jobs
Knowledge 3
add_agent_documentAttach a knowledge file to the agentnew
Upload a material the agent learns from (PDF, DOC, DOCX, TXT, XLS, XLSX, CSV, PPT, PPTX, KEY; ≤10 MB) from a public URL or base64. Used the next time the script is generated (generate_agent_script). Free.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Agent id (UUID), from list_agents |
filename | string | yes | File name with extension, e.g. "price-list.pdf" |
file_url | string | Public https URL of the file | |
file_base64 | string | Base64 content (use instead of file_url) |
generate_knowledge_from_urlTurn a web page into a knowledge blocknew
Read a public web page and return a knowledge text block about it. Costs 1 credit. NOT saved, append the result to company_description with set_agent_company if you want the agent to know it.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Agent id (UUID), from list_agents |
url | string | yes | Public http(s) page URL |
remove_agent_documentRemove a knowledge file from the agentnew
Remove an uploaded material from the agent (see get_agent → documents[].url). Free.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Agent id (UUID), from list_agents |
url | string | documents[].url (preferred) | |
filename | string | documents[].filename if you have no url |
Voice 3
list_retell_voicesList available Retell voices
Catalogue of voices that can be assigned via set_agent_voice. Read-only.
No arguments.
Calls: GET /v1/agents/voices/retell
list_voice_catalogList voices (Voice v2 catalogue)new
All voices for the website widget, Slide Presenter and Telegram voice: engine, gender, accent, expressive support, Ukrainian support, recommended flag, preview audio. Read-only. Use a voice_id from here in set_agent_voice / set_presentation_voice / set_telegram_voice_settings.
No arguments.
set_agent_voiceTurn on / configure the website-widget voiceupdated
Configure the voice of the agent's website widget exactly like the app's widget settings page and create/update the voice agent at the voice provider, so voice calls in the widget really work. Only the settings you pass change (the rest keep their saved values or the app defaults). voice_id is required the first time (list_voice_catalog). Takes up to ~1 min. Free to configure; widget voice calls cost 14 credits/min.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Agent id (UUID), from list_agents |
voice_id | string | Voice id from list_voice_catalog (e.g. "retell-Grace", "11labs-Adrian") | |
speed | number | Speaking pace, 0.7-1.3 (1 = normal) min 0.7; max 1.3 | |
emotion | string | Delivery: neutral, expressive (warm), calm, dynamic (energetic) one of: neutral, expressive, calm, dynamic | |
languages | string[] | Retell locales like ["en-US","uk-UA"]; more than one = multilingual (answers in the caller's language). [] or one locale = script language only max items 20 | |
rules | string | Communication rules (plain text instructions for how the agent talks). Omit to keep the saved ones max length 8000 | |
ambient | string | Background sound; "" = none one of: "", coffee-shop, call-center, convention-hall | |
ambient_sound_volume | number | min 0; max 2 | |
backchannel | boolean | Short "mm-hm" acknowledgements while the person talks | |
backchannel_frequency | number | min 0; max 1 | |
end_call_s | number | Hang up after this many seconds of silence min 10; max 120 | |
volume | number | min 0; max 2 | |
responsiveness | number | How fast the agent answers (1 = fastest) min 0; max 1 | |
interruption_sensitivity | number | min 0; max 1 | |
dynamic_voice_speed | boolean | ||
dynamic_responsiveness | boolean | ||
expressive_mode | boolean | Only works for voices marked expressive in list_voice_catalog | |
denoising_mode | string | one of: noise-cancellation, noise-and-background-speech-cancellation | |
reminder_seconds | number | Nudge a silent person after N seconds; 0 = off min 0; max 120 | |
handbook | object | Behaviour switches (only the ones you pass change) | |
handbook.conversational_personality | boolean | ||
handbook.natural_filler_words | boolean | ||
handbook.high_empathy | boolean | ||
handbook.echo_verification | boolean | ||
handbook.speech_normalization | boolean | ||
handbook.ai_disclosure | boolean | ||
handbook.scope_boundaries | boolean | ||
llm | string | Model: fast, balanced (default) or smart one of: fast, balanced, smart | |
screen_cards | boolean | Show interactive cards (buttons/forms) on screen during voice calls |
Channels (connections) 6
create_agent_connectionAdd a channel to an agentnew
Add a channel. WEBSITE_WIDGET needs website_url; new widgets get the app defaults (Modern design) plus your widget_config, then get_widget_embed_code. WEBSITE_BUTTON: just creates the channel (style it in get_widget_embed_code). PRESENTATION_WIDGET = publish the Slide Presenter (optional website_url). GOOGLE_MEET: prefer connect_google_meet. PHONE_CALL: prefer setup_phone_agent. Telegram → connect_telegram_bot; WhatsApp → connect_whatsapp_account. A second widget for the same URL updates the existing one. Free. Interactive cards in chat need design "modern"; in voice they need set_agent_voice screen_cards=true.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Agent id (UUID), from list_agents |
type | string | yes | one of: WEBSITE_WIDGET, WEBSITE_BUTTON, PRESENTATION_WIDGET, GOOGLE_MEET, PHONE_CALL |
name | string | ||
website_url | string | Site where the widget/presentation is installed | |
widget_config | object | Website widget settings (only what you pass changes; texts/theme are merged) | |
widget_config.design | string | modern = new skin with interactive cards in chat; classic = old look one of: classic, modern | |
widget_config.theme | object | Modern design look (merged with the saved theme) | |
widget_config.theme.glass | boolean | ||
widget_config.theme.glassOpacity | number | min 0; max 1 | |
widget_config.theme.blur | number | min 0; max 60 | |
widget_config.theme.headerStyle | string | one of: gradient, solid | |
widget_config.theme.gradient | string[] | min items 2; max items 3 | |
widget_config.theme.gradientAngle | number | min 0; max 360 | |
widget_config.theme.accentGradient | boolean | ||
widget_config.theme.openAnimation | string | one of: genie, spring, scale, slide, fade | |
widget_config.theme.voiceVisual | string | Look of the voice view: portrait, liquid or waves one of: portrait, liquid, waves | |
widget_config.position | string | one of: bottom-right, bottom-left, top-right, top-left | |
widget_config.primaryColor | string | pattern ^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$ | |
widget_config.backgroundColor | string | pattern ^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$ | |
widget_config.textColor | string | pattern ^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$ | |
widget_config.borderRadius | number | min 0; max 40 | |
widget_config.buttonColor | string | pattern ^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$ | |
widget_config.buttonTextColor | string | pattern ^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$ | |
widget_config.headerColor | string | pattern ^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$ | |
widget_config.headerTextColor | string | pattern ^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$ | |
widget_config.userBubbleColor | string | pattern ^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$ | |
widget_config.userBubbleTextColor | string | pattern ^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$ | |
widget_config.agentBubbleColor | string | pattern ^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$ | |
widget_config.agentBubbleTextColor | string | pattern ^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$ | |
widget_config.logoVariant | string | one of: white, blue, black | |
widget_config.launcherShape | string | one of: round, square | |
widget_config.launcher_image_url | string | Public URL of a small square picture for the widget button (PNG/JPG/WebP/GIF/SVG, ≤ ~110 KB) | |
widget_config.launcherImage | "" | "" = remove the custom button picture (Vendo logo again) | |
widget_config.collectName | boolean | ||
widget_config.collectEmail | boolean | ||
widget_config.collectPhone | boolean | ||
widget_config.teaser | boolean | Invitation: show autoOpenMessage as a bubble next to the closed button | |
widget_config.teaserDelay | number | min 3; max 30 | |
widget_config.autoOpen | boolean | Open the widget automatically after autoOpenDelay seconds | |
widget_config.autoOpenDelay | number | min 0; max 600 | |
widget_config.autoOpenSound | boolean | ||
widget_config.autoOpenMessage | string | max length 500 | |
widget_config.replySound | boolean | ||
widget_config.unreadBadge | boolean | ||
widget_config.navigationEnabled | boolean | Let the agent open pages of the site | |
widget_config.navigationMode | string | one of: auto, ask | |
widget_config.continueAfterNavigate | boolean | ||
widget_config.texts | object | Wording overrides; keys: welcomeMessage, welcomeFollowUp (greeting bubbles) and the contact form: contactTitle, contactSubtitle, nameLabel, namePlaceholder, nameRequired, emailLabel, emailPlaceholder, emailRequired, emailInvalid, phoneLabelRequired, phoneLabel, phonePlaceholder, phoneRequired, phoneInvalid, contactSubmit. "" or null = back to the default (auto-translated). Merged with saved texts. | |
google_meet_config | object | ||
google_meet_config.auto_join | boolean | ||
google_meet_config.join_before_minutes | number | Join N minutes before start (default 1) min 0; max 30 | |
google_meet_config.greeting_message | string | max length 1000 | |
google_meet_config.language | string | Meeting language code, e.g. "en" | |
google_meet_config.meeting_mode | string | presentation = shares the agent's slides (needs slides) one of: voice_only, presentation | |
phone_call_config | object | Voice Call texts only; number, voice and inbound are set with setup_phone_agent | |
phone_call_config.call_script | string | ||
phone_call_config.greeting_message | string | ||
phone_call_config.call_objective | string | one of: sell, inform, book_meeting, collect_info, qualify_lead | |
phone_call_config.max_call_duration_seconds | number | min 30; max 1800 | |
phone_call_config.post_call_actions | object | ||
phone_call_config.post_call_actions.log_outcome | boolean | ||
phone_call_config.post_call_actions.send_followup_email | boolean | ||
phone_call_config.post_call_actions.book_meeting_if_interested | boolean | ||
phone_call_config.post_call_actions.collect_contact_info | boolean |
delete_agent_connectionRemove a channel (irreversible)newexecute
Remove a channel from the agent (the embed code stops working). No undo. Only after preview_delete_agent_connection and explicit user approval.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Agent id (UUID), from list_agents |
connection_id | string | yes | Channel (connection) id (UUID), from list_agent_connections |
get_agent_connectionGet one channel of an agentnew
Full settings of one channel (website widget, button, presentation, Google Meet, phone, Telegram). Read-only.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Agent id (UUID), from list_agents |
connection_id | string | yes | Channel (connection) id (UUID), from list_agent_connections |
list_agent_connectionsList an agent's connections (channels)
See every channel attached to this agent: widgets, telegram, phone, presentations, etc.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes |
preview_delete_agent_connectionPreview removing a channelnewpreview
#preview_delete_agent_connection
Show what removing a channel would do. Nothing is removed. ALWAYS call first and show the user before delete_agent_connection.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Agent id (UUID), from list_agents |
connection_id | string | yes | Channel (connection) id (UUID), from list_agent_connections |
update_agent_connectionChange a channel's settingsnew
Change a channel: website widget look and behaviour (design, colours, button picture, invitation/auto-open, sounds, contact form and its wording, greetings, page navigation, voice view), name, website_url, Google Meet settings or phone texts. Only what you pass changes; widget texts and theme are merged. Free.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Agent id (UUID), from list_agents |
connection_id | string | yes | Channel (connection) id (UUID), from list_agent_connections |
name | string | ||
website_url | string | ||
widget_config | object | Website widget settings (only what you pass changes; texts/theme are merged) | |
widget_config.design | string | modern = new skin with interactive cards in chat; classic = old look one of: classic, modern | |
widget_config.theme | object | Modern design look (merged with the saved theme) | |
widget_config.theme.glass | boolean | ||
widget_config.theme.glassOpacity | number | min 0; max 1 | |
widget_config.theme.blur | number | min 0; max 60 | |
widget_config.theme.headerStyle | string | one of: gradient, solid | |
widget_config.theme.gradient | string[] | min items 2; max items 3 | |
widget_config.theme.gradientAngle | number | min 0; max 360 | |
widget_config.theme.accentGradient | boolean | ||
widget_config.theme.openAnimation | string | one of: genie, spring, scale, slide, fade | |
widget_config.theme.voiceVisual | string | Look of the voice view: portrait, liquid or waves one of: portrait, liquid, waves | |
widget_config.position | string | one of: bottom-right, bottom-left, top-right, top-left | |
widget_config.primaryColor | string | pattern ^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$ | |
widget_config.backgroundColor | string | pattern ^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$ | |
widget_config.textColor | string | pattern ^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$ | |
widget_config.borderRadius | number | min 0; max 40 | |
widget_config.buttonColor | string | pattern ^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$ | |
widget_config.buttonTextColor | string | pattern ^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$ | |
widget_config.headerColor | string | pattern ^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$ | |
widget_config.headerTextColor | string | pattern ^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$ | |
widget_config.userBubbleColor | string | pattern ^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$ | |
widget_config.userBubbleTextColor | string | pattern ^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$ | |
widget_config.agentBubbleColor | string | pattern ^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$ | |
widget_config.agentBubbleTextColor | string | pattern ^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$ | |
widget_config.logoVariant | string | one of: white, blue, black | |
widget_config.launcherShape | string | one of: round, square | |
widget_config.launcher_image_url | string | Public URL of a small square picture for the widget button (PNG/JPG/WebP/GIF/SVG, ≤ ~110 KB) | |
widget_config.launcherImage | "" | "" = remove the custom button picture (Vendo logo again) | |
widget_config.collectName | boolean | ||
widget_config.collectEmail | boolean | ||
widget_config.collectPhone | boolean | ||
widget_config.teaser | boolean | Invitation: show autoOpenMessage as a bubble next to the closed button | |
widget_config.teaserDelay | number | min 3; max 30 | |
widget_config.autoOpen | boolean | Open the widget automatically after autoOpenDelay seconds | |
widget_config.autoOpenDelay | number | min 0; max 600 | |
widget_config.autoOpenSound | boolean | ||
widget_config.autoOpenMessage | string | max length 500 | |
widget_config.replySound | boolean | ||
widget_config.unreadBadge | boolean | ||
widget_config.navigationEnabled | boolean | Let the agent open pages of the site | |
widget_config.navigationMode | string | one of: auto, ask | |
widget_config.continueAfterNavigate | boolean | ||
widget_config.texts | object | Wording overrides; keys: welcomeMessage, welcomeFollowUp (greeting bubbles) and the contact form: contactTitle, contactSubtitle, nameLabel, namePlaceholder, nameRequired, emailLabel, emailPlaceholder, emailRequired, emailInvalid, phoneLabelRequired, phoneLabel, phonePlaceholder, phoneRequired, phoneInvalid, contactSubmit. "" or null = back to the default (auto-translated). Merged with saved texts. | |
google_meet_config | object | ||
google_meet_config.auto_join | boolean | ||
google_meet_config.join_before_minutes | number | Join N minutes before start (default 1) min 0; max 30 | |
google_meet_config.greeting_message | string | max length 1000 | |
google_meet_config.language | string | Meeting language code, e.g. "en" | |
google_meet_config.meeting_mode | string | presentation = shares the agent's slides (needs slides) one of: voice_only, presentation | |
phone_call_config | object | Voice Call texts only; number, voice and inbound are set with setup_phone_agent | |
phone_call_config.call_script | string | ||
phone_call_config.greeting_message | string | ||
phone_call_config.call_objective | string | one of: sell, inform, book_meeting, collect_info, qualify_lead | |
phone_call_config.max_call_duration_seconds | number | min 30; max 1800 | |
phone_call_config.post_call_actions | object | ||
phone_call_config.post_call_actions.log_outcome | boolean | ||
phone_call_config.post_call_actions.send_followup_email | boolean | ||
phone_call_config.post_call_actions.book_meeting_if_interested | boolean | ||
phone_call_config.post_call_actions.collect_contact_info | boolean |
Website widget & button 2
create_widget_test_pageCreate a test page for a widgetnew
Create (or return the existing) private, unlisted test page with this website widget installed, to try it before adding it to a real site. Only the agent's creator can do this. Free.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Agent id (UUID), from list_agents |
connection_id | string | yes | Channel (connection) id (UUID), from list_agent_connections |
get_widget_embed_codeGet the embed code of a channelupdated
HTML to paste on a site for a channel: website widget (settings load from the server), website button (style it here: text, colours, radius, size, not stored server-side) or Slide Presenter (also the direct link). Read-only.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Agent id (UUID), from list_agents |
connection_id | string | yes | Channel (connection) id (UUID), from list_agent_connections |
text | string | Button only: label (default "Talk to Assistant") | |
background_color | string | Button only pattern ^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$ | |
text_color | string | Button only pattern ^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$ | |
border_radius | number | Button only (px) min 0; max 50 | |
size | string | Button only one of: small, medium, large | |
modal_primary_color | string | Button only: colour of the chat window it opens pattern ^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$ |
Slide Presenter 14
delete_narrator_leadDelete a presentation lead (irreversible)newexecute
Permanently delete a presentation lead. Only after preview_delete_narrator_lead and explicit user approval.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Agent id (UUID), from list_agents |
lead_id | string | yes |
generate_slide_scriptWrite the presenter script for a slidenew
AI-write what the presenter says on one slide, in the presentation's language. 1 credit (2 with image_url, the AI looks at the slide). NOT saved, put it into narrator_script with set_presentation_slides.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Agent id (UUID), from list_agents |
slide_number | integer | yes | min 1 |
total_slides | integer | yes | min 1 |
extracted_text | string | Text on the slide | |
previous_script | string | ||
next_slide_text | string | ||
image_url | string | Public URL of the slide image (optional, 2 credits) |
generate_transition_phrasesGenerate slide transition phrasesnew
Generate the phrases the Slide Presenter uses to move between slides, in the presentation's language, and save them on the agent. Needs saved slides (set_presentation_slides).
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Agent id (UUID), from list_agents |
get_narrator_analyticsPresentation analyticsupdated
Slide Presenter analytics. view "summary" (default): views, presentations started, watch time, unique viewers, average duration, completion rate, recent sessions, per channel. view "period" with date_from/date_to: widget loads, starts, watch minutes, unique viewers, credits used. Read-only. (No per-slide drop-off.)
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Agent id (UUID), from list_agents |
view | string | one of: summary, period | |
date_from | string | From (YYYY-MM-DD or ISO 8601) | |
date_to | string | To (YYYY-MM-DD or ISO 8601) |
get_presentationGet the agent's Slide Presenternew
The agent's presentation: PDF name, slides (text + presenter script; images with include_images), look, call-to-action, presenter voice, published channels and direct link. Read-only. (list_agent_presentations shows only the legacy creator.)
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Agent id (UUID), from list_agents |
include_images | boolean | Add slide image URLs |
get_presentation_embed_codeGet the presentation embed code & linknew
Direct link (narrator page) and the HTML snippet with the saved look. Publish it as a channel first (create_agent_connection type PRESENTATION_WIDGET). Read-only.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Agent id (UUID), from list_agents |
list_agent_presentationsGet the agent's presentation (alias)updated
Alias of get_presentation kept for older clients: returns the agent's current Slide Presenter deck (PDF name, slides with text and presenter script, look, call-to-action, voice, published channels, direct link), slides_count 0 when no deck is uploaded. Prefer get_presentation. Read-only.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Agent id (UUID), from list_agents |
include_images | boolean | Add slide image URLs |
list_narrator_leadsList presentation leadsupdated
Leads from the presentation call-to-action form (form data, page, status, notes). Filter by status, page with limit/offset. Read-only.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Agent id (UUID), from list_agents |
status | string | one of: new, contacted, converted, spam | |
limit | integer | min 1; max 500 | |
offset | integer | min 0 |
preview_delete_narrator_leadPreview deleting a presentation leadnewpreview
Show the lead that would be deleted. Nothing is deleted. Call first and show the user before delete_narrator_lead.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Agent id (UUID), from list_agents |
lead_id | string | yes |
set_presentation_ctaSet the presentation call-to-actionnew
The button viewers see: a lead form (fields) or a link. Only what you pass changes (fields replaces the whole field list). Leads land in list_narrator_leads. Free.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Agent id (UUID), from list_agents |
enabled | boolean | ||
buttonText | string | ||
type | string | one of: form, link | |
linkUrl | string | ||
linkOpenInNewTab | boolean | ||
successMessage | string | ||
fields | object[] | max items 12 | |
fields[].id | string | ||
fields[].type | string | yes | one of: text, email, phone, textarea, select |
fields[].label | string | yes | |
fields[].placeholder | string | ||
fields[].required | boolean | ||
fields[].options | string[] |
set_presentation_lookSet the presentation look & behaviournew
Theme, colours, corner radius, controls, autoplay, voice on/off, chat on/off, Modern look (background, glass, font, panel side, presenter bubble, voice visual), slide transition and button texts. Only what you pass changes. Free.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Agent id (UUID), from list_agents |
theme | string | one of: dark, light, custom | |
backgroundColor | string | pattern ^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$ | |
buttonColor | string | pattern ^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$ | |
buttonTextColor | string | pattern ^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$ | |
progressColor | string | pattern ^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$ | |
textColor | string | pattern ^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$ | |
borderRadius | number | min 0; max 40 | |
showControls | boolean | ||
autoplay | boolean | ||
voiceEnabled | boolean | Viewers can listen/talk by voice | |
chatEnabled | boolean | Viewers can chat | |
design | string | one of: classic, modern | |
modern | object | ||
modern.background | string | one of: solid, gradient | |
modern.bgFrom | string | pattern ^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$ | |
modern.bgTo | string | pattern ^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$ | |
modern.angle | number | min 0; max 360 | |
modern.glass | boolean | ||
modern.glassOpacity | number | min 0; max 1 | |
modern.blur | number | min 0; max 60 | |
modern.gradientButtons | boolean | ||
modern.accent2 | string | pattern ^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$ | |
modern.panelSide | string | one of: right, left | |
modern.presenterBubble | string | one of: off, left, right | |
modern.font | string | one of: system, rounded, serif, mono | |
modern.glow | boolean | ||
modern.voiceVisual | string | one of: portrait, liquid, waves | |
transition | string | one of: zoom, fade, slide | |
texts | object | "" or null = default text | |
texts.chooseMode | string | null | ||
texts.voice | string | null | ||
texts.chat | string | null | ||
texts.placeholder | string | null | ||
texts.clickToStart | string | null |
set_presentation_slidesSave the presentation slidesnew
Save the slide deck, REPLACES all slides (add, remove, reorder, edit). Each slide: image (public URL or base64; JPG/PNG/WebP/GIF ≤5 MB; omit to keep the stored image of an existing slide id), extracted text and presenter script. PDF upload is not supported by the API: render the PDF pages to images first. Then write scripts with generate_slide_script and set_presentation_voice. Free.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Agent id (UUID), from list_agents |
pdf_name | string | Name of the presentation, e.g. "Pitch deck.pdf" | |
slides | object[] | yes | min items 1; max items 100 |
slides[].id | string | Keep an existing slide id to keep its stored image | |
slides[].page_number | number | ||
slides[].image_url | string | ||
slides[].image_base64 | string | ||
slides[].extracted_text | string | ||
slides[].narrator_script | string | What the presenter says on this slide |
set_presentation_voiceSet the presenter's voicenew
The Slide Presenter's own voice and model (the website widget voice is not touched). Same settings as the app; only what you pass changes. Needs saved slides. Takes up to ~1 min. Free to configure; presentation voice costs 17 credits/min.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Agent id (UUID), from list_agents |
voice_id | string | Voice id from list_voice_catalog (e.g. "retell-Grace", "11labs-Adrian") | |
speed | number | Speaking pace, 0.7-1.3 (1 = normal) min 0.7; max 1.3 | |
emotion | string | Delivery: neutral, expressive (warm), calm, dynamic (energetic) one of: neutral, expressive, calm, dynamic | |
languages | string[] | Retell locales like ["en-US","uk-UA"]; more than one = multilingual (answers in the caller's language). [] or one locale = script language only max items 20 | |
rules | string | Communication rules (plain text instructions for how the agent talks). Omit to keep the saved ones max length 8000 | |
ambient | string | Background sound; "" = none one of: "", coffee-shop, call-center, convention-hall | |
ambient_sound_volume | number | min 0; max 2 | |
backchannel | boolean | Short "mm-hm" acknowledgements while the person talks | |
backchannel_frequency | number | min 0; max 1 | |
end_call_s | number | Hang up after this many seconds of silence min 10; max 120 | |
volume | number | min 0; max 2 | |
responsiveness | number | How fast the agent answers (1 = fastest) min 0; max 1 | |
interruption_sensitivity | number | min 0; max 1 | |
dynamic_voice_speed | boolean | ||
dynamic_responsiveness | boolean | ||
expressive_mode | boolean | Only works for voices marked expressive in list_voice_catalog | |
denoising_mode | string | one of: noise-cancellation, noise-and-background-speech-cancellation | |
reminder_seconds | number | Nudge a silent person after N seconds; 0 = off min 0; max 120 | |
handbook | object | Behaviour switches (only the ones you pass change) | |
handbook.conversational_personality | boolean | ||
handbook.natural_filler_words | boolean | ||
handbook.high_empathy | boolean | ||
handbook.echo_verification | boolean | ||
handbook.speech_normalization | boolean | ||
handbook.ai_disclosure | boolean | ||
handbook.scope_boundaries | boolean | ||
llm | string | Model: fast, balanced (default) or smart one of: fast, balanced, smart | |
screen_cards | boolean | Show interactive cards (buttons/forms) on screen during voice calls |
update_narrator_leadUpdate a presentation leadnew
Set a presentation lead's status (new / contacted / converted / spam) and notes. Free.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Agent id (UUID), from list_agents |
lead_id | string | yes | |
status | string | one of: new, contacted, converted, spam | |
notes | string |
Google Meet 8
connect_google_meetConnect Google Meet (invite address)updated
Turn on the Google Meet channel the way the app does: returns the agent's own invite address (agent-xxxxxxxx@meet.vendo-ai.com). Invite that address to any Google Calendar event with a Meet link and the agent joins automatically. No Google login needed. Also saves the meeting settings. Free to connect; meeting minutes are billed.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Agent id (UUID), from list_agents |
auto_join | boolean | ||
join_before_minutes | number | Join N minutes before start (default 1) min 0; max 30 | |
greeting_message | string | max length 1000 | |
language | string | Meeting language code, e.g. "en" | |
meeting_mode | string | presentation = shares the agent's slides (needs slides) one of: voice_only, presentation |
disconnect_google_meetDisconnect Google Meetupdatedexecute
Remove the Google Meet channel from the agent and turn auto-join off. Already scheduled meetings are not cancelled (use cancel_meeting). Only after preview_disconnect_google_meet and user approval.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Agent id (UUID), from list_agents |
get_google_meet_analyticsGoogle Meet analytics of an agentnew
Meetings, minutes, completed, missed and credits used for a date range. Read-only.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Agent id (UUID), from list_agents |
date_from | string | From (YYYY-MM-DD or ISO 8601) | |
date_to | string | To (YYYY-MM-DD or ISO 8601) |
get_google_meet_settingsGet Google Meet status & settingsnew
Whether Google Meet is connected for this agent, its invite address, settings and totals. Read-only.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Agent id (UUID), from list_agents |
list_google_meet_historyMeeting history of an agentnew
All meetings of this agent (newest first) with status, times, duration, whether a transcript exists. Paged. Read-only.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Agent id (UUID), from list_agents |
page | integer | min 1 | |
limit | integer | min 1; max 200 |
list_google_meet_meetingsList Google Meet meetings booked by this agent
Read-only list of meetings the agent has scheduled. Requires Google Meet connected first.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes |
preview_disconnect_google_meetPreview disconnecting Google Meetnewpreview
#preview_disconnect_google_meet
Show what disconnecting Google Meet would do. Nothing changes. Call first and show the user.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Agent id (UUID), from list_agents |
update_google_meet_settingsChange Google Meet settingsnew
Auto-join, minutes before start, greeting, language, meeting mode (voice only / presentation, needs slides). Only what you pass changes. Free.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Agent id (UUID), from list_agents |
auto_join | boolean | ||
join_before_minutes | number | Join N minutes before start (default 1) min 0; max 30 | |
greeting_message | string | max length 1000 | |
language | string | Meeting language code, e.g. "en" | |
meeting_mode | string | presentation = shares the agent's slides (needs slides) one of: voice_only, presentation |
Telegram 12
connect_telegram_botConnect a Telegram bot to the agent
The user creates a bot with @BotFather, gets the HTTP API token, and passes it here. After that, all messages to the bot are answered by the agent.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | |
bot_token | string | yes | Telegram Bot API token from @BotFather |
disconnect_telegram_botDisconnect Telegram botupdatedexecute
Detach the bot from this agent: the webhook is removed and the bot stops answering (it keeps existing in Telegram; chat history is kept; reconnect with connect_telegram_bot). Only after preview_disconnect_telegram_bot and the user's approval.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Agent id (UUID), from list_agents |
get_telegram_analyticsTelegram analyticsupdated
Chats, messages, voice calls, text messages, unique users and credits (text/voice) of the agent's Telegram bot for a date range. Read-only.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Agent id (UUID), from list_agents |
date_from | string | From (YYYY-MM-DD or ISO 8601) | |
date_to | string | To (YYYY-MM-DD or ISO 8601) |
get_telegram_bot_infoGet current Telegram bot info
See the connected bot username + settings.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes |
Calls: GET /v1/agents/:id/telegram
get_telegram_chatRead a Telegram chatnew
All messages of one Telegram chat, incl. voice-call turns with recording URL and duration. Read-only.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Agent id (UUID), from list_agents |
chat_id | string | yes | chats[].id from list_telegram_chats |
list_telegram_chatsList Telegram chatsnew
People/groups talking to the agent's Telegram bot: name, username, type, message count, last message time. Paged. Read-only.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Agent id (UUID), from list_agents |
page | integer | min 1 | |
limit | integer | min 1; max 200 |
preview_disconnect_telegram_botPreview disconnecting the Telegram botnewpreview
#preview_disconnect_telegram_bot
Show which Telegram bot would be detached from the agent (username, chats, messages). Nothing changes. Call first.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Agent id (UUID), from list_agents |
preview_send_telegram_messagePreview an operator Telegram messagenewpreview
#preview_send_telegram_message
Show the recipient and exact text of an operator message. Nothing is sent. ALWAYS call first and show the user the text.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Agent id (UUID), from list_agents |
chat_id | string | yes | |
message | string | yes | max length 4096 |
reconfigure_telegram_botRe-apply Telegram bot commandsnew
Re-apply the bot's commands and menu button (for private and group chats), use when the bot menu looks wrong. Free.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Agent id (UUID), from list_agents |
send_telegram_messageSend a Telegram message as the operatornewexecute
Sends a REAL Telegram message from the bot to this person (human takeover). Only after preview_send_telegram_message and the user approved the exact text.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Agent id (UUID), from list_agents |
chat_id | string | yes | |
message | string | yes | max length 4096 |
set_telegram_voice_callsTurn Telegram voice calls on/offnew
Show or hide the VOICE button of the agent's Telegram bot (voice calls in the mini app). Needs a connected bot. Free.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Agent id (UUID), from list_agents |
enabled | boolean | yes |
set_telegram_voice_settingsSet the Telegram voicenew
Voice and behaviour of the Telegram bot's voice calls (separate from the website widget): the same settings as set_agent_voice plus the mini app look (visual). Only what you pass changes. Needs a connected bot. Takes up to ~1 min. Free to configure; calls are billed per minute.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Agent id (UUID), from list_agents |
voice_id | string | Voice id from list_voice_catalog (e.g. "retell-Grace", "11labs-Adrian") | |
speed | number | Speaking pace, 0.7-1.3 (1 = normal) min 0.7; max 1.3 | |
emotion | string | Delivery: neutral, expressive (warm), calm, dynamic (energetic) one of: neutral, expressive, calm, dynamic | |
languages | string[] | Retell locales like ["en-US","uk-UA"]; more than one = multilingual (answers in the caller's language). [] or one locale = script language only max items 20 | |
rules | string | Communication rules (plain text instructions for how the agent talks). Omit to keep the saved ones max length 8000 | |
ambient | string | Background sound; "" = none one of: "", coffee-shop, call-center, convention-hall | |
ambient_sound_volume | number | min 0; max 2 | |
backchannel | boolean | Short "mm-hm" acknowledgements while the person talks | |
backchannel_frequency | number | min 0; max 1 | |
end_call_s | number | Hang up after this many seconds of silence min 10; max 120 | |
volume | number | min 0; max 2 | |
responsiveness | number | How fast the agent answers (1 = fastest) min 0; max 1 | |
interruption_sensitivity | number | min 0; max 1 | |
dynamic_voice_speed | boolean | ||
dynamic_responsiveness | boolean | ||
expressive_mode | boolean | Only works for voices marked expressive in list_voice_catalog | |
denoising_mode | string | one of: noise-cancellation, noise-and-background-speech-cancellation | |
reminder_seconds | number | Nudge a silent person after N seconds; 0 = off min 0; max 120 | |
handbook | object | Behaviour switches (only the ones you pass change) | |
handbook.conversational_personality | boolean | ||
handbook.natural_filler_words | boolean | ||
handbook.high_empathy | boolean | ||
handbook.echo_verification | boolean | ||
handbook.speech_normalization | boolean | ||
handbook.ai_disclosure | boolean | ||
handbook.scope_boundaries | boolean | ||
llm | string | Model: fast, balanced (default) or smart one of: fast, balanced, smart | |
screen_cards | boolean | Show interactive cards (buttons/forms) on screen during voice calls | |
visual | string | Look of the mini app during a call one of: portrait, liquid, waves |
Voice Call (phone) 8
generate_phone_scriptDraft a phone call scriptnew
Draft a call script and greeting from the agent's sales script for a call objective, in the agent's language. Free. Not saved, pass it to setup_phone_agent (or let setup_phone_agent generate it).
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Agent id (UUID), from list_agents |
call_objective | string | one of: sell, inform, book_meeting, collect_info, qualify_lead | |
voice_gender | string | one of: male, female |
get_phone_channel_statsVoice Call channel countersnew
Calls attempted/completed/successful, minutes and credits of one Voice Call channel. Read-only.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Agent id (UUID), from list_agents |
connection_id | string | yes | Channel (connection) id (UUID), from list_agent_connections |
list_phone_callsList the agent's phone callsnew
Phone calls of this agent (incl. test and inbound) with transcript, recording URL, summary, outcome, sentiment, duration and credits, plus totals by outcome/sentiment. Read-only.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Agent id (UUID), from list_agents |
limit | integer | min 1; max 500 |
list_phone_numbersList my phone numbersnew
Phone numbers already on your account (number, country, type, monthly cost, per-minute rate). Buying numbers is done in the app. Read-only.
No arguments.
Calls: GET /v1/phone/numbers
phone_test_callMake a test call (real phone)newexecute
DIALS A REAL PHONE: the agent calls to_number from the channel's number (max 2 min, 25 credits/min). Only call the user's own number, only after preview_phone_test_call and explicit approval.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Agent id (UUID), from list_agents |
to_number | string | yes | |
connection_id | string |
preview_phone_test_callPreview a test callnewpreview
Show which number would be called from which, and the cost (25 credits/min, max 2 min). Nothing is dialled. ALWAYS call first.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Agent id (UUID), from list_agents |
to_number | string | yes | E.164, e.g. +447700900123 |
connection_id | string |
preview_setup_phone_agentPreview the Voice Call setupnewpreview
Show what setup_phone_agent would do with the same arguments: which channel is created or updated, which of YOUR numbers is attached, objective, voice, inbound on/off. Nothing is attached or created. ALWAYS call first and show the user, then call setup_phone_agent with the same arguments after approval.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Agent id (UUID), from list_agents |
phone_number_id | string | Required the first time, from list_phone_numbers | |
connection_id | string | Existing PHONE_CALL channel; default = the agent's first one or a new one | |
call_objective | string | one of: sell, inform, book_meeting, collect_info, qualify_lead | |
call_script | string | ||
greeting_message | string | ||
voice_id | string | From list_retell_voices / list_voice_catalog | |
speed | number | min 0.7; max 1.3 | |
emotion | string | one of: neutral, expressive, calm, dynamic | |
language | string | auto = script language; multi = switch to the caller's language one of: auto, multi | |
communication_rules | string | ||
voice_gender | string | one of: male, female | |
accept_incoming | boolean | Answer calls TO this number with an inbound agent | |
ambient_sound | string | one of: "", coffee-shop, call-center, convention-hall | |
enable_backchannel | boolean | ||
end_call_silence_seconds | number | min 10; max 120 | |
reminder_trigger_seconds | number | min 5; max 60 | |
max_call_duration_seconds | number | min 30; max 1800 |
setup_phone_agentSet up the Voice Call channelupdatedexecute
Create/update the agent's Voice Call channel like the app: attach one of YOUR numbers (list_phone_numbers), objective, script (generated if omitted), greeting, voice, pace, emotion, language, rules, inbound calls on/off, background sound, silence/reminder timings. Creates the phone voice agent at the provider and links the number (with accept_incoming the number starts answering real calls). Takes up to ~1 min. Free to set up; calls cost 25 credits/min. Only call after preview_setup_phone_agent and the user's approval.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Agent id (UUID), from list_agents |
phone_number_id | string | Required the first time, from list_phone_numbers | |
connection_id | string | Existing PHONE_CALL channel; default = the agent's first one or a new one | |
call_objective | string | one of: sell, inform, book_meeting, collect_info, qualify_lead | |
call_script | string | ||
greeting_message | string | ||
voice_id | string | From list_retell_voices / list_voice_catalog | |
speed | number | min 0.7; max 1.3 | |
emotion | string | one of: neutral, expressive, calm, dynamic | |
language | string | auto = script language; multi = switch to the caller's language one of: auto, multi | |
communication_rules | string | ||
voice_gender | string | one of: male, female | |
accept_incoming | boolean | Answer calls TO this number with an inbound agent | |
ambient_sound | string | one of: "", coffee-shop, call-center, convention-hall | |
enable_backchannel | boolean | ||
end_call_silence_seconds | number | min 10; max 120 | |
reminder_trigger_seconds | number | min 5; max 60 | |
max_call_duration_seconds | number | min 30; max 1800 |
WhatsApp 5
connect_whatsapp_accountGet a WhatsApp connect linkupdatedexecute
Returns a hosted link; the user opens it and scans the QR code with WhatsApp (Linked devices), that links a REAL phone number to the workspace. Poll list_whatsapp_accounts until status is "ok". Free. Only after preview_connect_whatsapp_account and the user's approval.
No arguments.
disconnect_whatsapp_accountDisconnect WhatsApp (irreversible)newexecute
Disconnect and delete the WhatsApp account; auto-replies stop and reconnecting needs a new QR scan. Only after preview_disconnect_whatsapp_account and user approval.
| Argument | Type | Req. | Description |
|---|---|---|---|
account_id | string | yes |
list_whatsapp_accountsList WhatsApp accountsnew
WhatsApp accounts connected to your user (phone, name, status: connecting / ok / error / credentials / stopped). Account-level: auto-replies use your latest campaign's agent. Read-only.
No arguments.
Calls: GET /v1/whatsapp/accounts
preview_connect_whatsapp_accountPreview connecting WhatsAppnewpreview
#preview_connect_whatsapp_account
Explain what connecting a WhatsApp number does and list the accounts already connected. No link is created. Call first, then connect_whatsapp_account after the user approves.
No arguments.
preview_disconnect_whatsapp_accountPreview disconnecting WhatsAppnewpreview
#preview_disconnect_whatsapp_account
Show the WhatsApp account that would be disconnected. Nothing changes. Call first.
| Argument | Type | Req. | Description |
|---|---|---|---|
account_id | string | yes |
Conversations & recordings 6
get_conversation_recordingsGet voice recordings of a conversationnew
Recording URLs (WAV), duration and start time of the voice calls in one widget conversation (up to 10 calls). Read-only.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Agent id (UUID), from list_agents |
connection_id | string | yes | Channel (connection) id (UUID), from list_agent_connections |
conversation_id | string | yes | From list_connection_conversations |
Calls: GET /v1/agents/:id/connections/:cid/conversations/:conversationId/recording
get_linkedin_conversationOpen a LinkedIn conversationnew
All messages of one LinkedIn chat (chat_id from list_linkedin_conversations). Read-only.
| Argument | Type | Req. | Description |
|---|---|---|---|
chat_id | string | yes |
list_connection_conversationsList conversations of a channelnew
#list_connection_conversations
Conversations of a widget/presentation channel with messages, mode (chat/voice), visitor name/email/phone and page. Paged (limit ≤500). Read-only.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Agent id (UUID), from list_agents |
connection_id | string | yes | Channel (connection) id (UUID), from list_agent_connections |
page | integer | min 1 | |
limit | integer | min 1; max 500 |
list_linkedin_conversationsLinkedIn conversationsnew
LinkedIn chats of the workspace (optionally of one campaign and/or account): contact, last message, pending AI reply, human takeover flag, outreach status. Read-only.
| Argument | Type | Req. | Description |
|---|---|---|---|
campaign_id | string | ||
account_id | string | ||
limit | integer | min 1; max 100 | |
offset | integer | min 0 |
preview_send_linkedin_messagePreview a manual LinkedIn messagenewpreview
#preview_send_linkedin_message
Dry-run: recipient, exact text, cost (5 credits) and whether the balance suffices. Nothing is sent. Call before send_linkedin_message and show the text to the user.
| Argument | Type | Req. | Description |
|---|---|---|---|
chat_id | string | yes | |
text | string | yes |
send_linkedin_messageSend a LinkedIn message (REAL, 5 credits)newexecute
Send a REAL LinkedIn message in the chat; costs 5 credits, the AI stops messaging this contact (human takeover) and the CRM lead moves to Manager Dialog. Only after the user approved preview_send_linkedin_message.
| Argument | Type | Req. | Description |
|---|---|---|---|
chat_id | string | yes | |
text | string | yes |
Message previews 3
preview_agent_emailPreview an email the agent would writeupdatedexecute
Render a sample outreach email from an agent, no sending, no credits. Optional mode/prompt/tone/customInstructions/variant/followUp{index,delayDays,previous}/sample make it render exactly like a campaign would (same as preview_campaign_email).
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Agent id |
preview_linkedin_followupPreview a LinkedIn follow-up the agent would writeexecute
Render a sample first follow-up (after the connection is accepted). No sending, no cost.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes |
preview_linkedin_invitePreview a LinkedIn invite the agent would writeexecute
Render a sample LinkedIn connection request from this agent. No sending, no cost.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes |
Agent analytics & credits 4
get_agent_connection_statsChannel countersnew
Conversations, messages, unique visitors, last activity of one channel. For widgets prefer get_connection_analytics (fresher). Read-only.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Agent id (UUID), from list_agents |
connection_id | string | yes | Channel (connection) id (UUID), from list_agent_connections |
get_agents_dashboardLegacy agents dashboard numbersnew
Older overview numbers of all agents (superseded by get_analytics_agents). Read-only.
No arguments.
get_connection_analyticsWidget/button channel analyticsnew
For a website widget/button/presentation channel: widget loaded, activated, chats started, voice calls started, plus estimated credits (text messages, voice minutes) for a date range. Read-only.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Agent id (UUID), from list_agents |
connection_id | string | yes | Channel (connection) id (UUID), from list_agent_connections |
date_from | string | From (YYYY-MM-DD or ISO 8601) | |
date_to | string | To (YYYY-MM-DD or ISO 8601) |
list_agents_with_statsList agents with usage stats
Same as list_agents but includes per-agent stats (connections, credits spent, status).
No arguments.
Calls: GET /v1/agents/with-stats
Meetings tools 8
Meetings calendar 8
cancel_meetingCancel a meeting for the agentnewexecute
The agent will NOT join this scheduled meeting (the calendar event itself stays). Only scheduled, not yet started meetings. No undo. Only after preview_cancel_meeting and user approval.
| Argument | Type | Req. | Description |
|---|---|---|---|
meeting_id | string | yes |
get_meetingGet a meetingnew
One meeting with transcript, summary, action items, join/leave time and duration. Read-only.
| Argument | Type | Req. | Description |
|---|---|---|---|
meeting_id | string | yes |
Calls: GET /v1/meetings/:meetingId
get_meeting_transcriptGet a meeting transcriptnew
Transcript messages, summary and action items of a meeting (from the database or the recording service). Read-only.
| Argument | Type | Req. | Description |
|---|---|---|---|
meeting_id | string | yes |
list_meeting_agentsAgents with Google Meetnew
Agents that have Google Meet connected (the calendar's agent filter). Read-only.
No arguments.
Calls: GET /v1/meetings/agents
list_meetingsMeetings calendar (all agents)new
Google Meet meetings of all your agents in a date range (default: next 7 days, max 62 days), title, time, link, organizer, attendees, status, agent, has transcript. Read-only.
| Argument | Type | Req. | Description |
|---|---|---|---|
start | string | ISO date/time | |
end | string | ISO date/time | |
agent_id | string |
Calls: GET /v1/meetings
preview_cancel_meetingPreview cancelling a meeting for the agentnewpreview
Show which meeting the agent would skip. Nothing changes. Call first and show the user.
| Argument | Type | Req. | Description |
|---|---|---|---|
meeting_id | string | yes |
preview_remove_agent_from_meetingPreview removing the agent from a live callnewpreview
#preview_remove_agent_from_meeting
Show whether the agent is in this live meeting and what removing it does. Nothing changes.
| Argument | Type | Req. | Description |
|---|---|---|---|
meeting_id | string | yes |
remove_agent_from_meetingRemove the agent from a live callnewexecute
The agent leaves the live Google Meet call right now. Only works while the meeting is in progress. Only after preview_remove_agent_from_meeting and user approval.
| Argument | Type | Req. | Description |
|---|---|---|---|
meeting_id | string | yes |
Vendo SDR tools 75
Overview & live signals 4
sdr_company_heatHot companiesupdated
Read-only. Companies ranked by live signal heat, with ICP fit, signal types and contact status. One search (search_id, pages via offset up to any depth) or all searches merged (first 500 rows, offset + limit ≤ 500).
| Argument | Type | Req. | Description |
|---|---|---|---|
search_id | string (uuid) | ||
limit | integer | min 1; max 500 | |
offset | integer | min 0 |
Calls: GET /v1/sdr/company-heat
sdr_learning_insightsSignal Learning insightsupdated
Read-only. Learned weight per signal type and conversion from real outreach outcomes, for one search or per search across the workspace.
| Argument | Type | Req. | Description |
|---|---|---|---|
search_id | string (uuid) |
sdr_overviewSDR processes overviewnew
Read-only. Every SDR search of the workspace with signals (7/30 days), contacts, hot companies, hunter health (running/cooling/paused) and outreach, plus workspace totals and a daily series, the /vendo-sdr list page.
| Argument | Type | Req. | Description |
|---|---|---|---|
days | integer | Daily series length, default 14 min 7; max 60 |
Calls: GET /v1/sdr/overview
sdr_recent_signalsRecent signalsupdated
Read-only. Latest signals of one search (search_id) or merged across all searches of the workspace.
| Argument | Type | Req. | Description |
|---|---|---|---|
search_id | string (uuid) | ||
limit | integer | min 1; max 100 |
Calls: GET /v1/sdr/recent
Wizard & ICP research 9
sdr_find_citiesFind cities for an SDR searchnew
Read-only. Main cities of the given countries (no q), or a name search (q ≥ 2 Latin letters). Use the returned objects as target_cities in sdr_create_search / sdr_update_search.
| Argument | Type | Req. | Description |
|---|---|---|---|
countries | string[] | yes | ISO-2 or CC:SUB codes, e.g. ['GB','US:FL'] min items 1; max items 40 |
q | string | ||
limit | integer | Per country without q (max 30); total with q (max 50) min 1; max 50 |
Calls: GET /v1/sdr/geo/cities
sdr_generate_agent_signal_profileGenerate an agent's signal profile (Claude)
#sdr_generate_agent_signal_profile
Run Claude on the agent's knowledge to (re)generate its signal profile. Persists onto the agent so later Signal Searches reuse it.
| Argument | Type | Req. | Description |
|---|---|---|---|
agent_id | string | yes |
sdr_get_agent_signal_profileGet an agent's signal profile
Read the agent's current signal profile (ICP, offer summary, hunter weights). Returns { exists: false } when nothing's generated yet.
| Argument | Type | Req. | Description |
|---|---|---|---|
agent_id | string | yes |
sdr_get_researchPoll an SDR ICP researchnew
Read-only. Pass job_id (from sdr_start_research / a previous poll) or agent_id (while the company profile is still being built, or to get the latest research of a company). Returns status (queued/running/done/error), stages, events and, when done, the result: company dossier and the editable profile (search name, offer, ICP, decision roles, regions, keywords, LinkedIn queries, competitor pages, hunter plan). Poll every 5–10 s.
| Argument | Type | Req. | Description |
|---|---|---|---|
job_id | string (uuid) | ||
agent_id | string (uuid) |
sdr_get_research_audienceFirst buyers found by an ICP researchnew
Read-only. The instant audience of a finished research: up to ~20 matching people from the Vendo database with company, position, country, LinkedIn, email and why they fit, the wizard's "buyers already found" cards. refresh=true rebuilds it (slower).
| Argument | Type | Req. | Description |
|---|---|---|---|
job_id | string (uuid) | yes | |
refresh | boolean |
sdr_materialize_researchCopy a research's instant audience into a searchnew
Copies the instant audience of a finished research into a search as contacts (background, idempotent, filtered by the search's regions/cities). sdr_create_search with research_job_id already does this after activation; use this only to repeat it. Contacts are billed (5 credits each) like any other contact when used.
| Argument | Type | Req. | Description |
|---|---|---|---|
job_id | string (uuid) | yes | |
search_id | string (uuid) | yes |
sdr_preview_searchPreview a Signal Search profile (no save)execute
Run Claude on the agent's script and return the auto-generated ICP profile (offer summary, ideal customer profile, relevant topics, LinkedIn queries) WITHOUT persisting anything. Use to review/edit before calling sdr_create_search.
| Argument | Type | Req. | Description |
|---|---|---|---|
agent_id | string | yes |
sdr_preview_start_researchPreview: research a company website for a new SDRnewpreview
Dry run of the New SDR wizard's first steps. Tells whether a company profile for this website already exists (reused, 0 credits) or must be built (deep website scan 15 + outreach playbook 1 = 16 credits), and whether a recent ICP research (< 24 h) would be reused. Nothing is created. Always call before sdr_start_research and show the cost.
| Argument | Type | Req. | Description |
|---|---|---|---|
company_name | string | yes | |
website | string | yes | Domain or URL, e.g. acme.com |
force | boolean | Run a fresh research even if a recent one exists |
Calls: POST /v1/sdr/research
sdr_start_researchResearch a company website (New SDR wizard)newexecute
Starts the New SDR wizard for a company + website, exactly like the web app: reuses the company profile for the same domain or builds a hidden one (website deep scan, playbook; 16 credits), then runs the ICP research (1–3 min, not billed). Returns agent_id and either the research job or a setup state. Poll sdr_get_research until the job status is done, then call sdr_create_search with research_job_id. Only call after the user approved sdr_preview_start_research.
| Argument | Type | Req. | Description |
|---|---|---|---|
company_name | string | yes | |
website | string | yes | |
force | boolean |
Calls: POST /v1/sdr/research
Searches 10
sdr_activate_searchActivate (start) a Signal Search
Set status=active and fire-start all enabled hunters. Reversible via sdr_pause_search.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes |
sdr_create_searchCreate an SDR search (Create & Start)updated
Creates an SDR search like the wizard's "Create & Start". Preferred: research_job_id from a finished sdr_get_research, every field defaults to the research profile, regions to its regions (or HQ country), all 24 hunters on, competitor pages saved, then the search is activated and the research's first buyers are copied in. Legacy: agent_id + name + target_regions (profile generated from the company profile if offer/ICP omitted). start=false creates a draft without hunting. Hunting is not billed per run; each usable contact costs 5 credits. Errors with 409 if an identical search was just created.
| Argument | Type | Req. | Description |
|---|---|---|---|
research_job_id | string (uuid) | ||
agent_id | string (uuid) | Company profile id (legacy path, or must match the research's) | |
name | string | max length 200 | |
enabled_sources | string[] | Hunters to run. Omitted on create = all 24, exactly like the web wizard (includes paid engines and opt-in hunters). items: linkedin_jobs, linkedin_company, linkedin_activity, ai_web_hunter, social_signal_hunter, crunchbase_rss, hn_hiring, g2_reviews, tech_stack, connected_people, email_enricher, linkedin_engagement, linkedin_job_change, linkedin_competitor_followers, cross_pool, people_pool, github_issues, adzuna_jobs, corporate_registries, web_news_radar, web_pulse_hunter, google_places, web_profile, local_listings | |
target_regions | string[] | ISO-2 countries or US states as CC:SUB, e.g. ['GB','US:FL']. At least one. | |
target_cities | object[] | Optional cities inside target_regions (use sdr_find_cities). Empty = whole countries. max items 100 | |
target_cities[].country | string | yes | ISO-2 country code; must be one of target_regions min length 2; max length 2 |
target_cities[].name | string | yes | |
target_cities[].admin1 | string | ||
target_cities[].admin1_code | string | ||
target_cities[].lat | number | ||
target_cities[].lng | number | ||
offer_summary | string | "What you sell" | |
icp_summary | string | "Who buys". The decision-makers sentence and company-size line are added automatically. | |
decision_roles | string[] | Decision-maker job titles; used by Decision-maker matching and Website profile hunters max items 20 | |
company_size | string | e.g. "11–50 employees"; written into icp_summary as "Preferred company size: …" | |
industry_keywords | string[] | ||
exclude_keywords | string[] | ||
linkedin_search_queries | object[] | max items 50 | |
linkedin_search_queries[].query | string | yes | |
linkedin_search_queries[].location_hint | string | ||
linkedin_search_queries[].rationale | string | ||
competitor_pages | string[] | Competitor LinkedIn page URLs (Competitor Engagement and Competitor Switching hunters) max items 30 | |
relevant_topics | object | ||
signal_weights | object | ||
languages | string[] | Outreach languages; written into icp_summary | |
generation_reasoning | string | ||
generation_model | string | ||
start | boolean | Activate hunters right away (default true) |
Calls: POST /v1/sdr/searches
sdr_delete_searchDelete an SDR process (irreversible)updatedexecute
Deletes the SDR process and ALL its data: hunters stop, the outreach campaign is stopped and deleted, signals, contacts, hunter state, QA runs and learning weights are removed. Not reversible, no refund. Only after the user approved sdr_preview_delete_search.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string (uuid) | yes |
Calls: DELETE /v1/sdr/searches/:id
sdr_get_searchGet a Signal Search
Fetch one Signal Search by id, including its profile, hunters, and contact counts.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes |
Calls: GET /v1/sdr/searches/:id
sdr_list_searchesList Vendo SDR Signal Searches
List active Signal Searches in this workspace. Optionally filter by agent.
| Argument | Type | Req. | Description |
|---|---|---|---|
agent_id | string |
Calls: GET /v1/sdr/searches
sdr_pause_searchPause an SDR processupdated
Pauses the whole SDR process: every hunter AND all outreach sending (email and LinkedIn). Data is kept. Reversible with sdr_resume_search.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string (uuid) | yes |
sdr_preview_delete_searchPreview deleting an SDR processupdatedpreview
Dry run. Shows the process, how many contacts and signals would be lost and the outreach campaign status. Nothing is deleted. Call before sdr_delete_search.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string (uuid) | yes |
Calls: DELETE /v1/sdr/searches/:id
sdr_regenerate_searchRegenerate a Signal Search profile (re-run Claude)
Re-run Claude on the agent's current knowledge to regenerate the entire ICP profile and hunter configuration. Useful after the agent's script changed significantly.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes |
sdr_resume_searchResume an SDR processupdated
Resumes a paused SDR process: hunters restart and outreach that was paused together with the search resumes (outreach you paused separately stays paused).
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string (uuid) | yes |
sdr_update_searchEdit an SDR searchupdated
The wizard's Edit → "Save changes": name, hunters, regions, cities, offer, ICP, decision-makers, company size, keywords, exclusions, LinkedIn queries, competitor pages, hot threshold. Partial updates; status is not touched (an active search keeps hunting with the new profile). Status changes only via sdr_activate_search / sdr_pause_search / sdr_resume_search.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string (uuid) | yes | |
name | string | max length 200 | |
enabled_sources | string[] | Hunters to run. Omitted on create = all 24, exactly like the web wizard (includes paid engines and opt-in hunters). items: linkedin_jobs, linkedin_company, linkedin_activity, ai_web_hunter, social_signal_hunter, crunchbase_rss, hn_hiring, g2_reviews, tech_stack, connected_people, email_enricher, linkedin_engagement, linkedin_job_change, linkedin_competitor_followers, cross_pool, people_pool, github_issues, adzuna_jobs, corporate_registries, web_news_radar, web_pulse_hunter, google_places, web_profile, local_listings | |
target_regions | string[] | ISO-2 countries or US states as CC:SUB, e.g. ['GB','US:FL']. At least one. | |
target_cities | object[] | Optional cities inside target_regions (use sdr_find_cities). Empty = whole countries. max items 100 | |
target_cities[].country | string | yes | ISO-2 country code; must be one of target_regions min length 2; max length 2 |
target_cities[].name | string | yes | |
target_cities[].admin1 | string | ||
target_cities[].admin1_code | string | ||
target_cities[].lat | number | ||
target_cities[].lng | number | ||
offer_summary | string | "What you sell" | |
icp_summary | string | "Who buys". The decision-makers sentence and company-size line are added automatically. | |
decision_roles | string[] | Decision-maker job titles; used by Decision-maker matching and Website profile hunters max items 20 | |
company_size | string | e.g. "11–50 employees"; written into icp_summary as "Preferred company size: …" | |
industry_keywords | string[] | ||
exclude_keywords | string[] | ||
linkedin_search_queries | object[] | max items 50 | |
linkedin_search_queries[].query | string | yes | |
linkedin_search_queries[].location_hint | string | ||
linkedin_search_queries[].rationale | string | ||
competitor_pages | string[] | Competitor LinkedIn page URLs (Competitor Engagement and Competitor Switching hunters) max items 30 | |
relevant_topics | object | ||
signal_weights | object | ||
hot_threshold | integer | min 0; max 100 |
Calls: PATCH /v1/sdr/searches/:id
Hunters 13
sdr_configure_hunterConfigure one hunternew
Turns one hunter on/off and/or sets the search fields it reads (only those listed in its inputs): competitor_pages (Competitor Engagement/Switching), target_cities (local business hunters, email discovery), decision_roles (Decision-maker matching, Website profile), linkedin_search_queries (LinkedIn hiring), industry_keywords / exclude_keywords / relevant_topics. Settings are search-wide.
| Argument | Type | Req. | Description |
|---|---|---|---|
search_id | string (uuid) | yes | |
source | string | yes | Hunter source id (see sdr_list_hunters) one of: linkedin_jobs, linkedin_company, linkedin_activity, ai_web_hunter, social_signal_hunter, crunchbase_rss, hn_hiring, g2_reviews, tech_stack, connected_people, email_enricher, linkedin_engagement, linkedin_job_change, linkedin_competitor_followers, cross_pool, people_pool, github_issues, adzuna_jobs, corporate_registries, web_news_radar, web_pulse_hunter, google_places, web_profile, local_listings |
enabled | boolean | ||
settings | object | ||
settings.competitor_pages | string[] | Competitor LinkedIn page URLs (Competitor Engagement and Competitor Switching hunters) max items 30 | |
settings.target_cities | object[] | Optional cities inside target_regions (use sdr_find_cities). Empty = whole countries. max items 100 | |
settings.target_cities[].country | string | yes | ISO-2 country code; must be one of target_regions min length 2; max length 2 |
settings.target_cities[].name | string | yes | |
settings.target_cities[].admin1 | string | ||
settings.target_cities[].admin1_code | string | ||
settings.target_cities[].lat | number | ||
settings.target_cities[].lng | number | ||
settings.decision_roles | string[] | Decision-maker job titles; used by Decision-maker matching and Website profile hunters max items 20 | |
settings.linkedin_search_queries | object[] | max items 50 | |
settings.linkedin_search_queries[].query | string | yes | |
settings.linkedin_search_queries[].location_hint | string | ||
settings.linkedin_search_queries[].rationale | string | ||
settings.industry_keywords | string[] | ||
settings.exclude_keywords | string[] | ||
settings.relevant_topics | object |
sdr_healthGet Signal Search health dashboard
Operator-level health view: states, signals/contacts produced, credits spent, recent events, learning insights for a single search.
| Argument | Type | Req. | Description |
|---|---|---|---|
search_id | string | yes |
sdr_hunter_dailySignals & contacts per day for one hunternew
Read-only. The hunter page chart: signals and contacts per day for one hunter (days 2–60, default 14).
| Argument | Type | Req. | Description |
|---|---|---|---|
search_id | string (uuid) | yes | |
source | string | yes | Hunter source id (see sdr_list_hunters) one of: linkedin_jobs, linkedin_company, linkedin_activity, ai_web_hunter, social_signal_hunter, crunchbase_rss, hn_hiring, g2_reviews, tech_stack, connected_people, email_enricher, linkedin_engagement, linkedin_job_change, linkedin_competitor_followers, cross_pool, people_pool, github_issues, adzuna_jobs, corporate_registries, web_news_radar, web_pulse_hunter, google_places, web_profile, local_listings |
days | integer | min 2; max 60 |
sdr_hunter_detailHunter detail pagenew
Read-only. One hunter of a search: run state, signals in 30 days, signals & contacts per day (days 2–60, default 14), the contacts it found (incl. pending/phone-only), and its settings.
| Argument | Type | Req. | Description |
|---|---|---|---|
search_id | string (uuid) | yes | |
source | string | yes | Hunter source id (see sdr_list_hunters) one of: linkedin_jobs, linkedin_company, linkedin_activity, ai_web_hunter, social_signal_hunter, crunchbase_rss, hn_hiring, g2_reviews, tech_stack, connected_people, email_enricher, linkedin_engagement, linkedin_job_change, linkedin_competitor_followers, cross_pool, people_pool, github_issues, adzuna_jobs, corporate_registries, web_news_radar, web_pulse_hunter, google_places, web_profile, local_listings |
days | integer | min 2; max 60 | |
contacts_limit | integer | min 1; max 200 |
sdr_list_huntersList all SDR hunters (sources)updated
Read-only. The 24 hunter sources a search can run, with UI name, what each looks at, opt-in/paid-engine/LinkedIn-budget flags, whether it can be run by hand, and which search fields it reads. Use to map a hunter the user names ("Competitor Switching") to its id (g2_reviews) before sdr_set_hunters / sdr_configure_hunter / sdr_run_now. Hunters are not billed per run; each usable contact costs 5 credits.
No arguments.
Calls: GET /v1/sdr/hunters
sdr_pause_hunterPause ONE hunterupdated
Pauses one hunter of a search; the others keep running. Reversible with sdr_resume_hunter.
| Argument | Type | Req. | Description |
|---|---|---|---|
search_id | string (uuid) | yes | |
source | string | yes | Hunter source id (see sdr_list_hunters) one of: linkedin_jobs, linkedin_company, linkedin_activity, ai_web_hunter, social_signal_hunter, crunchbase_rss, hn_hiring, g2_reviews, tech_stack, connected_people, email_enricher, linkedin_engagement, linkedin_job_change, linkedin_competitor_followers, cross_pool, people_pool, github_issues, adzuna_jobs, corporate_registries, web_news_radar, web_pulse_hunter, google_places, web_profile, local_listings |
sdr_preview_run_nowPreview running hunters nowupdatedpreview
Dry run of the hunter "Run" button. Lists which hunters would run (one source, or every enabled runnable hunter when omitted), flags paid engines and LinkedIn-budget hunters. Nothing runs.
| Argument | Type | Req. | Description |
|---|---|---|---|
search_id | string (uuid) | yes | |
source | string | Hunter source id; cross_pool and people_pool run automatically and cannot be started by hand one of: linkedin_jobs, linkedin_company, linkedin_activity, ai_web_hunter, social_signal_hunter, crunchbase_rss, hn_hiring, g2_reviews, tech_stack, connected_people, email_enricher, linkedin_engagement, linkedin_job_change, linkedin_competitor_followers, github_issues, adzuna_jobs, corporate_registries, web_news_radar, web_pulse_hunter, google_places, web_profile, local_listings |
sdr_resume_hunterResume ONE hunterupdated
Resumes one paused hunter of a search.
| Argument | Type | Req. | Description |
|---|---|---|---|
search_id | string (uuid) | yes | |
source | string | yes | Hunter source id (see sdr_list_hunters) one of: linkedin_jobs, linkedin_company, linkedin_activity, ai_web_hunter, social_signal_hunter, crunchbase_rss, hn_hiring, g2_reviews, tech_stack, connected_people, email_enricher, linkedin_engagement, linkedin_job_change, linkedin_competitor_followers, cross_pool, people_pool, github_issues, adzuna_jobs, corporate_registries, web_news_radar, web_pulse_hunter, google_places, web_profile, local_listings |
sdr_run_nowRun hunters nowupdatedexecute
Starts (or wakes, if already running) one hunter or every enabled runnable hunter of a search. Paid engines use Vendo's provider budget, LinkedIn hunters the LinkedIn budget; contacts found cost 5 credits each. Call after sdr_preview_run_now.
| Argument | Type | Req. | Description |
|---|---|---|---|
search_id | string (uuid) | yes | |
source | string | Hunter source id; cross_pool and people_pool run automatically and cannot be started by hand one of: linkedin_jobs, linkedin_company, linkedin_activity, ai_web_hunter, social_signal_hunter, crunchbase_rss, hn_hiring, g2_reviews, tech_stack, connected_people, email_enricher, linkedin_engagement, linkedin_job_change, linkedin_competitor_followers, github_issues, adzuna_jobs, corporate_registries, web_news_radar, web_pulse_hunter, google_places, web_profile, local_listings |
sdr_run_statePrimary run state of a searchnew
Read-only. Overall run state of a search (running / paused / idle) with the current action.
| Argument | Type | Req. | Description |
|---|---|---|---|
search_id | string (uuid) | yes |
sdr_run_statesGet per-source hunter run-states
Per-source map of run state (running / paused / idle) for a Signal Search.
| Argument | Type | Req. | Description |
|---|---|---|---|
search_id | string | yes |
sdr_search_huntersHunters of one SDR searchnew
Read-only. Every hunter for a search: enabled or not, live run state, signals in 30 days, 7-day sparkline, and the current values of the settings it reads. The search page's hunter stations.
| Argument | Type | Req. | Description |
|---|---|---|---|
search_id | string (uuid) | yes |
sdr_set_huntersSwitch hunters on/off for a searchnew
Enables/disables hunters (the wizard's Hunters step). Pass enable/disable lists, or enabled_sources as the full list. On an active search new hunters start within ~10 min (budget-throttled ones ~30); disabled ones stop at their next cycle. To stop one immediately without disabling it use sdr_pause_hunter.
| Argument | Type | Req. | Description |
|---|---|---|---|
search_id | string (uuid) | yes | |
enable | string[] | items: linkedin_jobs, linkedin_company, linkedin_activity, ai_web_hunter, social_signal_hunter, crunchbase_rss, hn_hiring, g2_reviews, tech_stack, connected_people, email_enricher, linkedin_engagement, linkedin_job_change, linkedin_competitor_followers, cross_pool, people_pool, github_issues, adzuna_jobs, corporate_registries, web_news_radar, web_pulse_hunter, google_places, web_profile, local_listings | |
disable | string[] | items: linkedin_jobs, linkedin_company, linkedin_activity, ai_web_hunter, social_signal_hunter, crunchbase_rss, hn_hiring, g2_reviews, tech_stack, connected_people, email_enricher, linkedin_engagement, linkedin_job_change, linkedin_competitor_followers, cross_pool, people_pool, github_issues, adzuna_jobs, corporate_registries, web_news_radar, web_pulse_hunter, google_places, web_profile, local_listings | |
enabled_sources | string[] | items: linkedin_jobs, linkedin_company, linkedin_activity, ai_web_hunter, social_signal_hunter, crunchbase_rss, hn_hiring, g2_reviews, tech_stack, connected_people, email_enricher, linkedin_engagement, linkedin_job_change, linkedin_competitor_followers, cross_pool, people_pool, github_issues, adzuna_jobs, corporate_registries, web_news_radar, web_pulse_hunter, google_places, web_profile, local_listings |
People (contacts) & ICP QA 16
sdr_bulk_delete_peopleRemove several contacts (no restore)newexecute
Removes up to 500 contacts of one search. No restore. Only after sdr_preview_bulk_delete_people was approved.
| Argument | Type | Req. | Description |
|---|---|---|---|
search_id | string (uuid) | yes | |
ids | string (uuid)[] | yes | min items 1; max items 500 |
sdr_delete_personRemove a contact (no restore)newexecute
Removes one contact from the search: hidden from lists, counts, exports and outreach. No restore, no refund. Only after sdr_preview_delete_person was approved.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string (uuid) | yes | |
search_id | string (uuid) | yes |
Calls: DELETE /v1/sdr/people/:id
sdr_export_people_csvExport SDR contacts as CSV
Export the contacts of an SDR search as CSV text (name, title, email, LinkedIn, location, company). Contacts ONLY, deliberately carries no scores or signal reasoning, because this file leaves the platform. Returns the raw CSV; can be large. Read-only, free.
| Argument | Type | Req. | Description |
|---|---|---|---|
search_id | string | yes | |
company | string | ||
role | string | ||
min_relevance | number |
sdr_export_personas_to_audienceExport SDR contacts to an audienceupdated
#sdr_export_personas_to_audience
Creates a custom audience from a search's contacts with an email (segment fit by default, optional roles or explicit ids). No credits. The audience can then be used in any campaign.
| Argument | Type | Req. | Description |
|---|---|---|---|
search_id | string (uuid) | yes | |
name | string | yes | Name of the new audience min length 1; max length 200 |
fit_segment | string | one of: fit, fit_uncertain, all, uncertain, unfit, unchecked | |
only_with_email | boolean | ||
roles | string[] | ||
ids | string (uuid)[] |
sdr_get_person_detailWhy was this person picked?updated
Read-only. Provenance of one contact: hunter, query, triggering signal, scores and the reasoning chain. Needs the search_id the contact belongs to.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string (uuid) | yes | Contact id |
search_id | string (uuid) | yes |
sdr_icp_qa_autocorrectKeyword auto-correction on/offnew
Lets the ICP check rewrite the search's industry keywords automatically (default off).
| Argument | Type | Req. | Description |
|---|---|---|---|
search_id | string (uuid) | yes | |
enabled | boolean | yes |
sdr_icp_qa_historyICP check historynew
Read-only. Past ICP-QA runs (checked, fits, uncertain, unfit, quality %, keyword changes) and whether keyword auto-correction is on.
| Argument | Type | Req. | Description |
|---|---|---|---|
search_id | string (uuid) | yes | |
limit | integer | min 1; max 100 |
sdr_icp_qa_rollbackRoll keywords back to a QA runnew
Restores the search's industry_keywords to the snapshot taken before the given ICP-QA run (undo an auto-correction).
| Argument | Type | Req. | Description |
|---|---|---|---|
search_id | string (uuid) | yes | |
run_id | string (uuid) | yes |
sdr_icp_qa_runICP check nownew
People page "Check now": classifies unchecked contacts of a search as Fit / Uncertain / Not a fit (AI, not billed) and may suggest keyword changes. Returns the run, or null when nothing is unchecked.
| Argument | Type | Req. | Description |
|---|---|---|---|
search_id | string (uuid) | yes |
sdr_list_personasList SDR contactsupdated
Read-only. Contacts of a search, newest first. Filters: group (fits/uncertain/unfit/unchecked), source (contacts of one hunter, incl. pending/phone-only), company, role, min_relevance; paging up to 200.
| Argument | Type | Req. | Description |
|---|---|---|---|
search_id | string (uuid) | yes | |
group | string | one of: fits, uncertain, unfit, unchecked | |
source | string | Hunter source id (see sdr_list_hunters) one of: linkedin_jobs, linkedin_company, linkedin_activity, ai_web_hunter, social_signal_hunter, crunchbase_rss, hn_hiring, g2_reviews, tech_stack, connected_people, email_enricher, linkedin_engagement, linkedin_job_change, linkedin_competitor_followers, cross_pool, people_pool, github_issues, adzuna_jobs, corporate_registries, web_news_radar, web_pulse_hunter, google_places, web_profile, local_listings | |
company | string | ||
role | string | ||
min_relevance | integer | min 0; max 100 | |
limit | integer | min 1; max 200 | |
offset | integer | min 0 |
Calls: GET /v1/sdr/people
sdr_people_group_countsFit / Uncertain / Not a fit countsnew
Read-only. How many contacts are Fit, Uncertain, Not a fit and unchecked. only_with_email=false counts email-or-LinkedIn reachable contacts (what outreach "Who to contact" uses). Use before choosing icp_segment.
| Argument | Type | Req. | Description |
|---|---|---|---|
search_id | string (uuid) | yes | |
only_with_email | boolean |
sdr_persona_statsSDR contact statsupdated
Read-only. Contact totals, by role, top companies, daily series (7, 14 or 28 days), credits spent.
| Argument | Type | Req. | Description |
|---|---|---|---|
search_id | string (uuid) | yes | |
days | number | one of: 7, 14, 28 |
Calls: GET /v1/sdr/people/stats
sdr_preview_bulk_delete_peoplePreview removing several contactsnewpreview
#sdr_preview_bulk_delete_people
Dry run of multi-select delete: checks every id belongs to the search and counts them by group.
| Argument | Type | Req. | Description |
|---|---|---|---|
search_id | string (uuid) | yes | |
ids | string (uuid)[] | yes | min items 1; max items 500 |
sdr_preview_delete_personPreview removing a contactnewpreview
Dry run. Shows the contact that would be removed. Nothing changes.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string (uuid) | yes | |
search_id | string (uuid) | yes |
Calls: DELETE /v1/sdr/people/:id
sdr_preview_set_person_groupPreview moving a contact between groupsnewpreview
Dry run. Shows the move and whether it costs credits: moving a contact the ICP check left free to Fit charges 5 credits.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string (uuid) | yes | |
search_id | string (uuid) | yes | |
group | string | yes | one of: fits, uncertain, unfit |
sdr_set_person_groupMove a contact to Fit / Uncertain / Not a fitnewexecute
Overrides the ICP verdict for one contact. Moving a free (precheck-skipped) contact to Fit charges 5 credits (402 if the balance is too low). Call sdr_preview_set_person_group first when moving to fits.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string (uuid) | yes | |
search_id | string (uuid) | yes | |
group | string | yes | one of: fits, uncertain, unfit |
Outreach 13
sdr_delete_outreachDelete the SDR outreach campaign (irreversible)
Delete the SDR outreach campaign. The Signal Search and its hunters CONTINUE running. Only call after sdr_preview_delete_outreach.
| Argument | Type | Req. | Description |
|---|---|---|---|
search_id | string | yes |
sdr_get_outreachGet SDR outreach status for a search
Read the SDR outreach campaign, channels enabled, ready/queued/sent counts, current content config.
| Argument | Type | Req. | Description |
|---|---|---|---|
search_id | string | yes |
sdr_get_outreach_contentGet SDR outreach content (templates)
Read the configured email subject/body templates and LinkedIn invite template for SDR outreach.
| Argument | Type | Req. | Description |
|---|---|---|---|
search_id | string | yes |
sdr_launch_outreachLaunch SDR outreach (sends real messages)updatedexecute
Launches outreach: real emails and/or LinkedIn invites start in the working window and new contacts keep being picked up. Each newly queued contact costs 5 credits. Managed senders need a paid plan; client_owned needs a connected mailbox. Only after the user approved sdr_preview_launch_outreach.
| Argument | Type | Req. | Description |
|---|---|---|---|
search_id | string (uuid) | yes | |
channels | string[] | items: email, linkedin | |
email_sender_mode | string | managed = Vendo sender pool (paid plans only); client_owned = the workspace's own connected mailboxes one of: managed, client_owned | |
client_email_provider | string | null | one of: google, microsoft | |
selected_linkedin_account_ids | string[] | Ids from sdr_get_outreach → linkedin_accounts; empty = every connected account in status OK | |
timezone | string | IANA time zone, e.g. Europe/London | |
work_start_hour | integer | min 0; max 23 | |
work_end_hour | integer | min 0; max 23 | |
exclude_weekends | boolean | ||
daily_email_limit | integer | min 1; max 500 | |
daily_linkedin_limit | integer | min 1; max 200 | |
branch_id | string | null | CRM branch for replies: a branch id from crm_list_branches, or null/'general' for General | |
icp_segment | string | Who to contact: fit (Fit + unchecked, default), fit_uncertain, all; legacy: uncertain, unfit, unchecked one of: fit, fit_uncertain, all, uncertain, unfit, unchecked |
sdr_linkedin_connect_linkGet a LinkedIn connect link for SDR outreachnew
Returns a hosted link where the user connects a LinkedIn account for this SDR's outreach. A human must open it; then pick the account in selected_linkedin_account_ids.
| Argument | Type | Req. | Description |
|---|---|---|---|
search_id | string (uuid) | yes |
sdr_list_outreach_processesSDR processes on the Sales Process pagenew
Read-only. SDR outreach processes of the workspace as listed on the Sales Process page (status, channels, counters).
No arguments.
sdr_pause_outreachPause SDR outreach delivery
Stop sending new messages on this SDR campaign. Reversible.
| Argument | Type | Req. | Description |
|---|---|---|---|
search_id | string | yes |
sdr_preview_delete_outreachPreview deleting an SDR outreach campaignpreview
Show what would happen. The Signal Search ITSELF is NOT deleted, only the outreach campaign.
| Argument | Type | Req. | Description |
|---|---|---|---|
search_id | string | yes |
sdr_preview_launch_outreachPreview launching SDR outreachupdatedpreview
Dry run of "Save and Launch". Shows the effective settings (request + saved + defaults), contacts in the chosen segment, ready email/LinkedIn contacts, predicted blockers (no LinkedIn account, no own mailbox), the paid-plan rule for managed senders and the delivery window. Nothing is sent. Always call first and show it to the user.
| Argument | Type | Req. | Description |
|---|---|---|---|
search_id | string (uuid) | yes | |
channels | string[] | items: email, linkedin | |
email_sender_mode | string | managed = Vendo sender pool (paid plans only); client_owned = the workspace's own connected mailboxes one of: managed, client_owned | |
client_email_provider | string | null | one of: google, microsoft | |
selected_linkedin_account_ids | string[] | Ids from sdr_get_outreach → linkedin_accounts; empty = every connected account in status OK | |
timezone | string | IANA time zone, e.g. Europe/London | |
work_start_hour | integer | min 0; max 23 | |
work_end_hour | integer | min 0; max 23 | |
exclude_weekends | boolean | ||
daily_email_limit | integer | min 1; max 500 | |
daily_linkedin_limit | integer | min 1; max 200 | |
branch_id | string | null | CRM branch for replies: a branch id from crm_list_branches, or null/'general' for General | |
icp_segment | string | Who to contact: fit (Fit + unchecked, default), fit_uncertain, all; legacy: uncertain, unfit, unchecked one of: fit, fit_uncertain, all, uncertain, unfit, unchecked |
sdr_preview_outreach_contentPreview a personalized SDR messageexecute
Render an example personalized email/LinkedIn message for a specific contact. Costs ~1 Claude call.
| Argument | Type | Req. | Description |
|---|---|---|---|
search_id | string | yes |
sdr_resume_outreachResume SDR outreach delivery
Resume a paused SDR outreach campaign.
| Argument | Type | Req. | Description |
|---|---|---|---|
search_id | string | yes |
sdr_save_outreach_settingsSave SDR outreach settings (no launch)new
Outreach window 1 "Save": Who to contact (icp_segment), channels, email sender mode/provider, LinkedIn accounts, time zone, working hours, weekends, daily limits, CRM branch. Partial: missing fields keep their saved values. Never launches: a new process stays a draft; a running one keeps its status and applies the change at once (widening Who to contact on a running process queues more contacts at 5 credits each). Set dry_run=true to preview.
| Argument | Type | Req. | Description |
|---|---|---|---|
search_id | string (uuid) | yes | |
channels | string[] | items: email, linkedin | |
email_sender_mode | string | managed = Vendo sender pool (paid plans only); client_owned = the workspace's own connected mailboxes one of: managed, client_owned | |
client_email_provider | string | null | one of: google, microsoft | |
selected_linkedin_account_ids | string[] | Ids from sdr_get_outreach → linkedin_accounts; empty = every connected account in status OK | |
timezone | string | IANA time zone, e.g. Europe/London | |
work_start_hour | integer | min 0; max 23 | |
work_end_hour | integer | min 0; max 23 | |
exclude_weekends | boolean | ||
daily_email_limit | integer | min 1; max 500 | |
daily_linkedin_limit | integer | min 1; max 200 | |
branch_id | string | null | CRM branch for replies: a branch id from crm_list_branches, or null/'general' for General | |
icp_segment | string | Who to contact: fit (Fit + unchecked, default), fit_uncertain, all; legacy: uncertain, unfit, unchecked one of: fit, fit_uncertain, all, uncertain, unfit, unchecked | |
dry_run | boolean |
sdr_update_outreach_contentEdit SDR outreach messagesupdated
Outreach windows 2–3: email (mode ai/fixed/html, prompt, tone, ≤5 fixed variants with {{placeholders}}, per-step follow-ups, own-HTML steps, signature, auto-reply) and LinkedIn (invite on/off, ai/exact texts, follow-up, delay after accept, conversation rules, auto-reply delays, signature, Calendly). Partial; takes effect on new sends. Follow-up changes are blocked while an email sequence is already sending.
| Argument | Type | Req. | Description |
|---|---|---|---|
search_id | string (uuid) | yes | |
email | object | ||
email.mode | string | ai = model writes each email from prompt; fixed = variants; html = html_steps one of: ai, fixed, html | |
email.prompt | string | ||
email.tone | string | one of: formal, casual, friendly | |
email.variants | object[] | Fixed texts, rotated per recipient. Placeholders: {{first_name}} {{last_name}} {{full_name}} {{company_name}} {{position}} {{city}} {{country}} max items 5 | |
email.variants[].subject | string | yes | |
email.variants[].body | string | yes | |
email.followups | object[] | Per-step follow-ups; delay counts from the previous email max items 7 | |
email.followups[].delay_days | integer | yes | min 1; max 90 |
email.followups[].prompt | string | ||
email.followups[].subject | string | ||
email.followups[].body | string | ||
email.html_steps | object[] | max items 20 | |
email.html_steps[].subject | string | yes | |
email.html_steps[].html | string | yes | |
email.html_steps[].delay_days | integer | ||
email.signature_enabled | boolean | ||
email.signature_text | string | ||
email.followups_enabled | boolean | ||
email.max_followups | integer | min 0; max 7 | |
email.followup_interval_days | integer | min 1; max 90 | |
email.auto_reply_enabled | boolean | ||
email.auto_reply_delay_minutes | integer | min 1; max 120 | |
email.validation_note_enabled | boolean | ||
linkedin | object | ||
linkedin.invite_enabled | boolean | ||
linkedin.invite_mode | string | one of: ai, exact | |
linkedin.invite_tone | string | one of: formal, casual, friendly | |
linkedin.invite_prompt | string | ||
linkedin.invite_exact_text | string | ||
linkedin.followup_enabled | boolean | ||
linkedin.followup_mode | string | one of: ai, exact | |
linkedin.followup_prompt | string | ||
linkedin.followup_exact_text | string | ||
linkedin.followup_cta | string | ||
linkedin.followup_delay_minutes | integer | Minutes between accepted invite and first message min 0; max 20160 | |
linkedin.calendly_url | string | ||
linkedin.conversation_rules | string | What we want from these conversations: goal, CTA, links. Applied to AI invites, follow-ups and smart replies. max length 3000 | |
linkedin.auto_reply_enabled | boolean | ||
linkedin.reply_delay_min_minutes | integer | min 1; max 60 | |
linkedin.reply_delay_max_minutes | integer | min 1; max 120 | |
linkedin.signature_enabled | boolean | ||
linkedin.signature_text | string | ||
linkedin.signature_mode | string | one of: first_message, every_message |
Search analytics 10
sdr_activity_feedLive activity feed for an SDR search
Recent discovery + outreach events for one SDR search, newest first, the same ticker the dashboard shows. Read-only, free.
| Argument | Type | Req. | Description |
|---|---|---|---|
search_id | string | yes | |
limit | number | Default 40 |
sdr_company_detailHot company popupnew
Read-only. One company of a search: company card (website, size, location, links), decision-makers found with fit, other known people at the company and its live signals. part=card returns only the card and people pool (slower source, up to 20 s).
| Argument | Type | Req. | Description |
|---|---|---|---|
search_id | string (uuid) | yes | |
company_id | string (uuid) | yes | company_id from sdr_company_heat |
part | string | one of: full, card | |
name | string | ||
domain | string |
sdr_daily_by_hunterSignals per day per hunternew
Read-only. Station sparklines: signals per day for every hunter of a search (days 2–60, default 14).
| Argument | Type | Req. | Description |
|---|---|---|---|
search_id | string (uuid) | yes | |
days | integer | min 2; max 60 |
sdr_eventsLive hunter events (polling)new
Read-only. What the hunters are doing right now (queries, companies resolved, signals saved, contacts found). Pass since = next_since of the previous call to get only new events. Best effort: last 150 events per search, lost on restart.
| Argument | Type | Req. | Description |
|---|---|---|---|
search_id | string (uuid) | yes | |
since | string | ISO timestamp | |
limit | integer | min 1; max 200 |
sdr_export_companies_csvExport companies CSVnew
Read-only. CSV of every company the search found: Company, Domain, Heat, ICP fit, Signal count, Signal types, Contact status, Contacts found.
| Argument | Type | Req. | Description |
|---|---|---|---|
search_id | string (uuid) | yes |
sdr_icp_reviewICP Review panelnew
Read-only. Uncertain contacts waiting for re-check, last run results, totals and next scheduled run.
| Argument | Type | Req. | Description |
|---|---|---|---|
search_id | string (uuid) | yes |
sdr_icp_review_runRe-check Uncertain contacts nownew
ICP Review "Re-check now": re-judges this search's Uncertain contacts (bounded per run, one run at a time). Contacts moved to Fit become billable (5 credits) when used.
| Argument | Type | Req. | Description |
|---|---|---|---|
search_id | string (uuid) | yes |
sdr_learning_recomputeRecompute signal-learning weights for a search
Recalculate per-signal-type weights from outreach outcomes (replies/meetings vs sends) for one search. Safe, no cost; use when the user asks to refresh/recalculate Signal Learning.
| Argument | Type | Req. | Description |
|---|---|---|---|
search_id | string | yes |
sdr_outreach_summarySent/queued counters for an SDR search
Per-channel sent and queued counts for one SDR search. Read-only, free.
| Argument | Type | Req. | Description |
|---|---|---|---|
search_id | string | yes |
sdr_signal_statsSignal stats by hunterupdated
Read-only. KPI cards: fresh signals, signals and contacts by source over the last N days (1–90, default 30).
| Argument | Type | Req. | Description |
|---|---|---|---|
search_id | string (uuid) | yes | |
days | integer | min 1; max 90 |
Campaigns tools 77
Lifecycle 15
archive_campaignArchive a campaignupdatedexecute
Archive the campaign: status becomes completed, all its outreach stops and it is hidden from the default lists (history stays readable with include_archived). Not reversible. Only call after preview_archive_campaign and the user's explicit approval.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Campaign id (UUID) from list_campaigns |
create_campaignCreate a campaign (DRAFT, not launched)updated
Create a classic campaign in DRAFT, nobody is contacted until launch_campaign. Needs an audience (audienceId from save_audience/list_audiences, OR customAudienceId of a READY uploaded/CRM list) and should get an agentId (a campaign cannot be launched without an agent: AGENT_REQUIRED). branchId = CRM branch for replies (crm_list_branches; null = General). Settings (all optional): channels, dailyLimit, dailyEmailLimit, dailyLinkedinLimit, timezone, workingHours, excludeWeekends, campaignDurationDays, customInstructions (Communication Rules incl. form of address), emailSignature, notifyEmails, ccEmail, autoResponseEnabled, autoResponseDelayMinutes, followUpInterval, maxFollowUps, trackOpens, emailSenderMode (managed needs a paid plan), senderEmailIds, emailContent {mode, prompt, tone, variants, htmlSteps, followUps[{delayDays, prompt|subject+body}]}, linkedinContent {mode, invite_prompt, invite_tone, invite_exact_text, followup_delay_minutes, followup_exact_text}, selectedLinkedinAccountIds, linkedinAutoReplyDisabled. Ids are checked against the workspace. Free, no sending.
| Argument | Type | Req. | Description |
|---|---|---|---|
name | string | yes | |
audienceId | string | Campaign audience id | |
customAudienceId | string | Instead of audienceId: id of a READY custom audience (uploaded CSV/CRM list); an audience is created from it | |
agentId | string | Sales agent id (list_agents). Required before launch. | |
branchId | string | null | CRM branch id for replies; null = General | |
description | string | ||
settings | object | ||
settings.channels | string[] | Channels the campaign uses (default ['email']). items: email, linkedin, phone_call | |
settings.dailyLimit | integer | New contacts emailed per day. Must not exceed sender capacity (list_sender_emails / list_client_mailboxes). Default 200. min 0 | |
settings.dailyEmailLimit | integer | Emails per day; the app keeps it equal to dailyLimit. min 0 | |
settings.dailyLinkedinLimit | integer | Campaign-wide LinkedIn invites per day across ALL its accounts (hard limit). Each account also has its own warm-up cap. min 0 | |
settings.timezone | string | IANA timezone of the sending window, e.g. Europe/London (default). | |
settings.workingHours | array | [startHour, endHour] in the campaign timezone (default [9,18]). min items 2; max items 2 | |
settings.excludeWeekends | boolean | Do not send on Saturday/Sunday (default true). | |
settings.campaignDurationDays | integer | Planned duration in days (default 30). min 1 | |
settings.customInstructions | string | Communication Rules: CTA, links, promises, what never to say. Overrides prompt defaults in emails, follow-ups, AI auto-replies and LinkedIn. Also sets the informal/formal form of address (e.g. ти/Ви, du/Sie) when the rules say so. max length 6000 | |
settings.emailSignature | string | HTML signature appended to emails ('' = no signature). | |
settings.notifyEmails | string[] | Addresses notified when a lead replies. | |
settings.ccEmail | string | Address put in CC of outgoing emails. | |
settings.autoResponseEnabled | boolean | AI answers email replies automatically (default true). | |
settings.autoResponseDelayMinutes | integer | Delay before the AI email auto-reply (default 10). min 0 | |
settings.followUpInterval | integer | Legacy: days between follow-ups, used only when emailContent.followUps is empty (default 3). min 0 | |
settings.maxFollowUps | integer | Legacy: number of follow-ups when emailContent.followUps is empty (default 3). min 0; max 7 | |
settings.trackOpens | boolean | Insert the open-tracking pixel. | |
settings.trackLinks | boolean | Saved like the app (currently not read by the sender). | |
settings.stopOnNegative | boolean | Saved like the app (currently not read by the sender). | |
settings.speedTier | string | one of: conservative, balanced, aggressive | |
settings.emailSenderMode | string | managed = Vendo warmed pool (PAID PLAN REQUIRED to launch); client_owned = the workspace's own mailboxes (connect_client_mailbox). Default managed. one of: managed, client_owned | |
settings.clientEmailProvider | string | null | Informational provider of own mailboxes. one of: google, microsoft | |
settings.senderEmailIds | string[] | Restrict sending to these sender ids from list_sender_emails (empty = auto-assign at launch). | |
settings.emailContent | object | Email content (wizard step 4). On a running campaign a change to steps/delays is NOT applied once anything was sent; see settings.smartleadSequencePending in get_campaign. | |
settings.emailContent.mode | string | yes | ai = AI writes each email from the prompt; fixed = your text with {{first_name}}-style placeholders; html = your own HTML steps. one of: ai, fixed, html |
settings.emailContent.prompt | string | AI writing rules for the first email. | |
settings.emailContent.tone | string | one of: formal, casual, friendly | |
settings.emailContent.variants | object[] | Fixed mode: up to 5 A/B variants of the first email. max items 5 | |
settings.emailContent.htmlSteps | object[] | HTML mode: one entry per email; delayDays = days after the previous email (not on the first). max items 20 | |
settings.emailContent.followUps | object[] | Follow-ups after the first email (max 7). max items 7 | |
settings.linkedinContent | object | LinkedIn content (wizard step 5). | |
settings.linkedinContent.mode | string | yes | one of: ai, fixed |
settings.linkedinContent.invite_prompt | string | AI rules for the connection note (also used for the form of address). | |
settings.linkedinContent.invite_tone | string | one of: formal, casual, friendly | |
settings.linkedinContent.invite_exact_text | string | Fixed connection note (LinkedIn sends max 200 chars). max length 300 | |
settings.linkedinContent.followup_delay_minutes | integer | Delay of the first message after the invite is accepted. min 0; max 43200 | |
settings.linkedinContent.followup_exact_text | string | Fixed first message after connect. max length 1500 | |
settings.selectedLinkedinAccountIds | string[] | LinkedIn accounts used by this campaign (ids from list_linkedin_accounts). Empty = all connected accounts at launch. | |
settings.linkedinAutoReplyDisabled | boolean | Switch LinkedIn AI auto-replies OFF for this campaign only (per-account settings: update_linkedin_account_settings). |
Calls: POST /v1/campaigns
delete_campaignDelete a campaign (irreversible)
Permanently delete a campaign. NOT reversible. Only call after the user has reviewed preview_delete_campaign output and explicitly approved.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes |
Calls: DELETE /v1/campaigns/:id
duplicate_campaignDuplicate a campaignnew
Copy a campaign (settings + audience) into a new DRAFT named '<name> (Copy)'. Nothing is sent. Note: the copy re-uses company-based audiences only and has no CRM branch; check get_campaign of the copy before launching.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Campaign id (UUID) from list_campaigns |
get_campaignGet a campaignupdated
Full campaign: status, settings (all wizard fields), sequence, channel paused flags, channelStatuses, today's sends, stats.totalContacts. Read-only.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Campaign id (UUID) from list_campaigns |
Calls: GET /v1/campaigns/:id
launch_campaignLaunch / resume a campaign (sends real outreach)updatedexecute
Start (or resume a paused) campaign exactly like the app: starts email sending and queues LinkedIn on the selected (or all connected) accounts. Contacts receive REAL outreach in the working hours; credits are spent as messages go out. Only call AFTER the user reviewed preview_launch_campaign and approved it. Returns the campaign plus linkedin_activation.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Campaign id (UUID) from list_campaigns |
linkedin_account_ids | string[] |
list_campaignsList campaignsupdated
List classic Sales Process campaigns with status and live stats. Archived campaigns are hidden unless include_archived=true. Read-only.
| Argument | Type | Req. | Description |
|---|---|---|---|
include_archived | boolean | Also return archived campaigns |
Calls: GET /v1/campaigns
pause_campaignPause a campaign (all channels)
Pause a running campaign across every channel. Reversible, call launch_campaign to resume.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes |
Calls: POST /v1/campaigns/:id/pause
pause_channelPause one channel of a campaign
Pause a single channel (email, linkedin, phone_call, voice_call) on a running campaign. Other channels keep going.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | |
channel | string | yes | one of: email, linkedin, phone_call, voice_call |
preview_archive_campaignPreview archiving a campaignnewpreview
Show what archiving would do, NOTHING CHANGES: current status and channels, what stops (an active campaign stops ALL outreach at once and releases its mailboxes / LinkedIn accounts), what is kept (history, threads, stats, CRM leads). Archiving cannot be undone (duplicate_campaign to run the same setup again). ALWAYS call first and show the user before archive_campaign.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Campaign id (UUID) from list_campaigns |
preview_delete_campaignPreview deleting a campaignpreview
Show what would happen if you delete a campaign. The campaign is NOT deleted.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes |
Calls: DELETE /v1/campaigns/:id
preview_launch_campaignPreview launching a campaignupdatedpreview
Readiness report for launch/resume, NOTHING IS SENT: ready flag, blockers (AGENT_REQUIRED, AUDIENCE_REQUIRED, AUDIENCE_EMPTY (0 contacts), CLIENT_MAILBOX_NOT_CONNECTED, PAID_PLAN_REQUIRED for Vendo managed senders, INVALID_STATUS), warnings, real recipient count, channels, daily email/LinkedIn limits, sender mode, email steps, schedule and the LinkedIn accounts that will be activated. ALWAYS call this first and show the result to the user before launch_campaign.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Campaign id (UUID) from list_campaigns |
linkedin_account_ids | string[] | Override which LinkedIn accounts get activated (default: settings.selectedLinkedinAccountIds, else all connected) |
resume_channelResume one channel of a campaign
Resume a paused channel on a campaign.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | |
channel | string | yes | one of: email, linkedin, phone_call, voice_call |
sync_campaign_statsRefresh campaign email countersnew
Pull the latest sent/opened/clicked/replied/bounced counters from the sending provider and store them. Max once per minute per campaign. No credits.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Campaign id (UUID) from list_campaigns |
update_campaignUpdate campaign (wizard settings)updated
Change a campaign's name, description or any wizard setting (partial: send only what changes; settings are merged). agentId and branchId can be changed only while the campaign is a draft. Settings (all optional): channels, dailyLimit, dailyEmailLimit, dailyLinkedinLimit, timezone, workingHours, excludeWeekends, campaignDurationDays, customInstructions (Communication Rules incl. form of address), emailSignature, notifyEmails, ccEmail, autoResponseEnabled, autoResponseDelayMinutes, followUpInterval, maxFollowUps, trackOpens, emailSenderMode (managed needs a paid plan), senderEmailIds, emailContent {mode, prompt, tone, variants, htmlSteps, followUps[{delayDays, prompt|subject+body}]}, linkedinContent {mode, invite_prompt, invite_tone, invite_exact_text, followup_delay_minutes, followup_exact_text}, selectedLinkedinAccountIds, linkedinAutoReplyDisabled. On a running campaign, changed email steps/delays are not applied once anything was sent (settings.smartleadSequencePending). No sending, no credits.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Campaign id (UUID) from list_campaigns |
name | string | ||
description | string | ||
agentId | string | Draft only | |
branchId | string | null | Draft only; null = General | |
settings | object | ||
settings.channels | string[] | Channels the campaign uses (default ['email']). items: email, linkedin, phone_call | |
settings.dailyLimit | integer | New contacts emailed per day. Must not exceed sender capacity (list_sender_emails / list_client_mailboxes). Default 200. min 0 | |
settings.dailyEmailLimit | integer | Emails per day; the app keeps it equal to dailyLimit. min 0 | |
settings.dailyLinkedinLimit | integer | Campaign-wide LinkedIn invites per day across ALL its accounts (hard limit). Each account also has its own warm-up cap. min 0 | |
settings.timezone | string | IANA timezone of the sending window, e.g. Europe/London (default). | |
settings.workingHours | array | [startHour, endHour] in the campaign timezone (default [9,18]). min items 2; max items 2 | |
settings.excludeWeekends | boolean | Do not send on Saturday/Sunday (default true). | |
settings.campaignDurationDays | integer | Planned duration in days (default 30). min 1 | |
settings.customInstructions | string | Communication Rules: CTA, links, promises, what never to say. Overrides prompt defaults in emails, follow-ups, AI auto-replies and LinkedIn. Also sets the informal/formal form of address (e.g. ти/Ви, du/Sie) when the rules say so. max length 6000 | |
settings.emailSignature | string | HTML signature appended to emails ('' = no signature). | |
settings.notifyEmails | string[] | Addresses notified when a lead replies. | |
settings.ccEmail | string | Address put in CC of outgoing emails. | |
settings.autoResponseEnabled | boolean | AI answers email replies automatically (default true). | |
settings.autoResponseDelayMinutes | integer | Delay before the AI email auto-reply (default 10). min 0 | |
settings.followUpInterval | integer | Legacy: days between follow-ups, used only when emailContent.followUps is empty (default 3). min 0 | |
settings.maxFollowUps | integer | Legacy: number of follow-ups when emailContent.followUps is empty (default 3). min 0; max 7 | |
settings.trackOpens | boolean | Insert the open-tracking pixel. | |
settings.trackLinks | boolean | Saved like the app (currently not read by the sender). | |
settings.stopOnNegative | boolean | Saved like the app (currently not read by the sender). | |
settings.speedTier | string | one of: conservative, balanced, aggressive | |
settings.emailSenderMode | string | managed = Vendo warmed pool (PAID PLAN REQUIRED to launch); client_owned = the workspace's own mailboxes (connect_client_mailbox). Default managed. one of: managed, client_owned | |
settings.clientEmailProvider | string | null | Informational provider of own mailboxes. one of: google, microsoft | |
settings.senderEmailIds | string[] | Restrict sending to these sender ids from list_sender_emails (empty = auto-assign at launch). | |
settings.emailContent | object | Email content (wizard step 4). On a running campaign a change to steps/delays is NOT applied once anything was sent; see settings.smartleadSequencePending in get_campaign. | |
settings.emailContent.mode | string | yes | ai = AI writes each email from the prompt; fixed = your text with {{first_name}}-style placeholders; html = your own HTML steps. one of: ai, fixed, html |
settings.emailContent.prompt | string | AI writing rules for the first email. | |
settings.emailContent.tone | string | one of: formal, casual, friendly | |
settings.emailContent.variants | object[] | Fixed mode: up to 5 A/B variants of the first email. max items 5 | |
settings.emailContent.htmlSteps | object[] | HTML mode: one entry per email; delayDays = days after the previous email (not on the first). max items 20 | |
settings.emailContent.followUps | object[] | Follow-ups after the first email (max 7). max items 7 | |
settings.linkedinContent | object | LinkedIn content (wizard step 5). | |
settings.linkedinContent.mode | string | yes | one of: ai, fixed |
settings.linkedinContent.invite_prompt | string | AI rules for the connection note (also used for the form of address). | |
settings.linkedinContent.invite_tone | string | one of: formal, casual, friendly | |
settings.linkedinContent.invite_exact_text | string | Fixed connection note (LinkedIn sends max 200 chars). max length 300 | |
settings.linkedinContent.followup_delay_minutes | integer | Delay of the first message after the invite is accepted. min 0; max 43200 | |
settings.linkedinContent.followup_exact_text | string | Fixed first message after connect. max length 1500 | |
settings.selectedLinkedinAccountIds | string[] | LinkedIn accounts used by this campaign (ids from list_linkedin_accounts). Empty = all connected accounts at launch. | |
settings.linkedinAutoReplyDisabled | boolean | Switch LinkedIn AI auto-replies OFF for this campaign only (per-account settings: update_linkedin_account_settings). |
Calls: PATCH /v1/campaigns/:id
Wizard previews 3
preview_campaign_emailPreview a campaign email (first or follow-up #k)newexecute
Render the exact email a campaign would send, first email or follow-up #k, with the writing rules, tone, Communication Rules or fixed text. Pass campaign_id to use its saved settings (any field you pass overrides them), or agent_id + rules for an unsaved draft. No sending, no credits.
| Argument | Type | Req. | Description |
|---|---|---|---|
campaign_id | string | ||
agent_id | string | Required for AI mode when no campaign_id | |
mode | string | one of: ai, fixed | |
prompt | string | AI writing rules (for the follow-up when follow_up is set) | |
tone | string | one of: formal, casual, friendly | |
custom_instructions | string | Communication Rules | |
variant | object | Fixed mode text | |
variant.subject | string | yes | |
variant.body | string | yes | |
follow_up | object | Preview follow-up #index instead of the first email | |
follow_up.index | integer | yes | min 1; max 7 |
follow_up.delay_days | integer | min 1; max 90 | |
follow_up.previous | object | ||
follow_up.previous.subject | string | ||
follow_up.previous.body | string | ||
sample | object | Sample recipient used for the preview (defaults to a demo contact). | |
sample.contact_name | string | ||
sample.company_name | string | ||
sample.position | string | ||
sample.city | string | ||
sample.country | string | ||
sample.website | string |
preview_campaign_linkedin_followupPreview the LinkedIn message after connectnewexecute
#preview_campaign_linkedin_followup
Render the first LinkedIn message sent after an invite is accepted (AI from the agent, or the fixed text with placeholders filled). Pass campaign_id to use saved settings. No sending, no credits.
| Argument | Type | Req. | Description |
|---|---|---|---|
campaign_id | string | ||
agent_id | string | ||
mode | string | one of: ai, fixed | |
exact_text | string | max length 1500 | |
sample | object | Sample recipient used for the preview (defaults to a demo contact). | |
sample.contact_name | string | ||
sample.company_name | string | ||
sample.position | string | ||
sample.city | string | ||
sample.country | string | ||
sample.website | string |
preview_campaign_linkedin_invitePreview a campaign LinkedIn invitenewexecute
#preview_campaign_linkedin_invite
Render the LinkedIn connection note a campaign would send (AI from rules/tone, or fixed text with placeholders), with its length vs the 200-char limit. Pass campaign_id to use saved settings. No sending, no credits.
| Argument | Type | Req. | Description |
|---|---|---|---|
campaign_id | string | ||
agent_id | string | ||
mode | string | one of: ai, fixed | |
prompt | string | ||
tone | string | one of: formal, casual, friendly | |
exact_text | string | max length 300 | |
sample | object | Sample recipient used for the preview (defaults to a demo contact). | |
sample.contact_name | string | ||
sample.company_name | string | ||
sample.position | string | ||
sample.city | string | ||
sample.country | string | ||
sample.website | string |
Emails, replies & delivery 13
get_campaign_emailOpen one campaign emailnew
One sent email by email id (from list_campaign_emails / list_campaign_replies): content, recipient, related open/click/reply events, the reply and the conversation. Read-only.
| Argument | Type | Req. | Description |
|---|---|---|---|
email_id | string | yes |
list_campaign_email_leadsEngaged email leadsnew
Recipients with their open/click/reply counts, read live from the sending provider (throttled: once per 15 s per campaign). Read-only.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Campaign id (UUID) from list_campaigns |
filter | string | one of: opened, clicked, replied, all |
list_campaign_emailsList sent / queued / prepared emailsnew
state=sent: emails already sent (filter by status, search by address); state=queued: scheduled/queued emails waiting to go out; state=prepared: pre-generated emails pipeline with status counts and preparation progress. Read-only.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Campaign id (UUID) from list_campaigns |
state | string | yes | one of: sent, queued, prepared |
status | string | sent: sent|delivered|opened|clicked|replied|bounced; prepared: pipeline status e.g. ready_to_send, scheduled | |
search | string | Search by email address (sent/queued) | |
limit | integer | Page size (default 50) min 1; max 200 | |
offset | integer | Rows to skip (default 0) min 0 |
Calls: GET /v1/campaigns/:id/emails/sent, GET /v1/campaigns/:id/emails/queued, GET /v1/campaigns/:id/emails/prepared
list_campaign_repliesCampaign replies inboxnew
Email replies received by a campaign with body and classification: real (a person answered), auto_reply (out of office), bounce. Read-only.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Campaign id (UUID) from list_campaigns |
type | string | one of: real, auto_reply, bounce |
prepare_campaign_emailsPre-generate campaign emails (legacy)new
Generate emails for every contact ahead of sending (legacy company-based audiences only; people/custom-audience campaigns prepare automatically after launch). Can take minutes. Nothing is sent. No credits.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Campaign id (UUID) from list_campaigns |
preview_reply_to_campaign_emailPreview a manual reply to a leadnewpreview
#preview_reply_to_campaign_email
Dry-run of replying to a lead's email reply: shows recipient, subject and the exact text. Nothing is sent. Call before reply_to_campaign_email and show the text to the user.
| Argument | Type | Req. | Description |
|---|---|---|---|
email_id | string | yes | |
body | string | yes | Reply body (HTML allowed) |
preview_schedule_campaign_deliveryPreview scheduling prepared emailsnewpreview
#preview_schedule_campaign_delivery
Dry-run: how many ready prepared emails would be scheduled into the campaign working hours. Nothing changes. Call before schedule_campaign_delivery.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Campaign id (UUID) from list_campaigns |
preview_send_campaign_emails_nowPreview sending prepared emails nownewpreview
#preview_send_campaign_emails_now
Dry-run: how many prepared emails would be sent immediately (outside the schedule). Nothing is sent.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Campaign id (UUID) from list_campaigns |
limit | integer | Max emails (default 10) min 1; max 100 |
refresh_campaign_email_replyReload a reply from the providernew
Fetch the latest reply text for an email from the sending provider (when a reply shows as empty). No credits, nothing sent.
| Argument | Type | Req. | Description |
|---|---|---|---|
email_id | string | yes |
reply_to_campaign_emailSend a manual reply to a lead (REAL email)newexecute
Send the reply immediately in the lead's thread (bypasses daily limits). REAL email. Only after the user approved the exact text from preview_reply_to_campaign_email.
| Argument | Type | Req. | Description |
|---|---|---|---|
email_id | string | yes | |
body | string | yes |
retry_campaign_email_preparationRetry failed email preparationnew
#retry_campaign_email_preparation
Put prepared emails that failed validation/generation back in the queue (max 3 attempts each). Nothing is sent.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Campaign id (UUID) from list_campaigns |
schedule_campaign_deliverySchedule prepared emails for deliverynewexecute
Spread all ready prepared emails over the campaign's working-hour slots; they are then SENT for real automatically. Only after the user approved preview_schedule_campaign_delivery.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Campaign id (UUID) from list_campaigns |
send_campaign_emails_nowSend prepared emails now (REAL emails)newexecute
Send up to limit prepared emails immediately, ignoring the schedule. REAL emails to real people. Only after the user approved preview_send_campaign_emails_now.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Campaign id (UUID) from list_campaigns |
limit | integer | min 1; max 100 |
AI conversations (threads) 6
close_campaign_threadClose an AI email conversationnew
Mark the conversation closed (no more AI replies). Nothing is sent.
| Argument | Type | Req. | Description |
|---|---|---|---|
thread_id | string | yes |
get_campaign_threadOpen an AI email conversationnew
One conversation with all messages (AI, human and contact). Read-only.
| Argument | Type | Req. | Description |
|---|---|---|---|
thread_id | string | yes |
list_campaign_threadsAI email conversations of a campaignnew
Conversations the AI is handling for a campaign (filter by status ai_handling|human_takeover|closed and sentiment positive|objection|referral|unsubscribe|neutral), plus counters by status and sentiment. Only threads of the API key's user. Read-only.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Campaign id (UUID) from list_campaigns |
status | string | one of: ai_handling, human_takeover, closed | |
sentiment | string | one of: positive, objection, referral, unsubscribe, neutral | |
limit | integer | Page size (default 50) min 1; max 200 | |
offset | integer | Rows to skip (default 0) min 0 |
Calls: GET /v1/campaigns/:id/threads, GET /v1/campaigns/:id/threads/stats
preview_reply_in_campaign_threadPreview a human reply in a conversationnewpreview
#preview_reply_in_campaign_thread
Dry-run: recipient, subject (defaults to Re: last subject) and the exact text. Nothing is sent. Call before reply_in_campaign_thread and show the text to the user.
| Argument | Type | Req. | Description |
|---|---|---|---|
thread_id | string | yes | |
body | string | yes | Plain text |
subject | string | ||
body_html | string |
reply_in_campaign_threadSend a human reply in a conversation (REAL email)newexecute
Send the email to the contact now. REAL email. Only after the user approved preview_reply_in_campaign_thread. Consider takeover_campaign_thread first so the AI stops answering.
| Argument | Type | Req. | Description |
|---|---|---|---|
thread_id | string | yes | |
body | string | yes | |
subject | string | ||
body_html | string |
takeover_campaign_threadTake over an AI email conversationnew
Stop the AI from replying in this conversation; a human continues (reply_in_campaign_thread). Nothing is sent.
| Argument | Type | Req. | Description |
|---|---|---|---|
thread_id | string | yes |
LinkedIn accounts, conversations & campaigns 17
activate_campaign_linkedinActivate LinkedIn on a campaignnewexecute
Queue the campaign audience for LinkedIn on the given (or selected/all connected) accounts and add linkedin to the channels. On an active campaign REAL connection invites start within the next cycles. Only after the user approved preview_activate_campaign_linkedin.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Campaign id (UUID) from list_campaigns |
account_ids | string[] |
add_campaign_linkedin_accountAdd a LinkedIn account to a campaignnew
#add_campaign_linkedin_account
Add a connected account to a running campaign; pending contacts are rebalanced across accounts and backfilled. It starts sending invites in the next cycles. No credits.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Campaign id (UUID) from list_campaigns |
account_id | string | yes |
connect_linkedin_accountGet a link to connect a LinkedIn account
Returns a hosted URL that a HUMAN must open in a browser to authorise a LinkedIn account. This tool cannot complete the connection itself, give the link to the user. No account exists until they finish the flow.
| Argument | Type | Req. | Description |
|---|---|---|---|
campaign_id | string | Optionally bind the new account to a campaign |
disconnect_linkedin_accountDisconnect a LinkedIn account
Remove a LinkedIn account from the workspace. Any campaign or SDR search sending from it stops immediately and daily capacity drops. Only call after the user reviewed preview_disconnect_linkedin_account and explicitly approved.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes |
get_campaign_linkedin_statsLinkedIn stats of a campaignnew
LinkedIn counters (total, pending, invites sent, connected, messaged, replied, failed), invites sent today, effective daily limit and active accounts; optionally for one account. Read-only.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Campaign id (UUID) from list_campaigns |
account_id | string |
get_linkedin_account_settingsLinkedIn account settingsnew
#get_linkedin_account_settings
Per-account LinkedIn settings shared by every campaign and SDR process using the account: AI auto-replies (on/off, tone, instructions, pitch, booking link, reply delays, daily limit, working hours, agent, contact info), invites (on/off, tone, pitch, instructions, CTA), follow-ups (on/off, pitch, instructions, CTA) and signature (text, first_message|every_message). The first read creates defaults (pre-filled from campaign_id's agent).
| Argument | Type | Req. | Description |
|---|---|---|---|
account_id | string | yes | |
campaign_id | string | Pre-fill defaults from this campaign's agent |
list_campaign_linkedin_accountsLinkedIn accounts working a campaignnew
#list_campaign_linkedin_accounts
Accounts working this campaign with per-account counters (pending, invites sent, connected, messaged, replied). Read-only.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Campaign id (UUID) from list_campaigns |
list_campaign_linkedin_outreachLinkedIn outreach of a campaignnew
#list_campaign_linkedin_outreach
Contacts in the campaign's LinkedIn queue with their status (pending, invite_sent, connected, message_sent, replied, failed, skipped), optionally for one account. Read-only.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Campaign id (UUID) from list_campaigns |
status | string | one of: pending, invite_sent, connected, message_sent, replied, failed, skipped | |
account_id | string | ||
limit | integer | Page size (default 50) min 1; max 200 | |
offset | integer | Rows to skip (default 0) min 0 |
list_linkedin_accountsList connected LinkedIn accountsupdated
LinkedIn accounts of the workspace and their status (ok = can send; ~70–80 invites/day each). Returns { role, accounts } (role is filled only with detailed=true). detailed=true adds owner/role, campaigns and SDR searches using each account, 7-day activity, prospecting caps, warm-up stage, premium tier and proxy country. Read-only.
| Argument | Type | Req. | Description |
|---|---|---|---|
detailed | boolean |
Calls: GET /v1/linkedin/accounts
preview_activate_campaign_linkedinPreview activating LinkedIn on a campaignnewpreview
#preview_activate_campaign_linkedin
Dry-run: which accounts would get the campaign audience queued, how many contacts and what is already queued. Nothing changes. Call before activate_campaign_linkedin.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Campaign id (UUID) from list_campaigns |
account_ids | string[] | Default: settings.selectedLinkedinAccountIds, else all connected accounts |
preview_disconnect_linkedin_accountPreview disconnecting a LinkedIn account (dry-run)preview
#preview_disconnect_linkedin_account
Dry-run of removing a LinkedIn account, showing the capacity that would be lost. ALWAYS call this first.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes |
preview_linkedin_account_messagesSample LinkedIn messages for an accountnewexecute
#preview_linkedin_account_messages
Sample invite, follow-up and auto-reply plus suggested pitch/signature for an account in a campaign (built from the campaign's agent). No sending, no credits.
| Argument | Type | Req. | Description |
|---|---|---|---|
account_id | string | yes | |
campaign_id | string | yes |
Calls: GET /v1/linkedin/accounts/:accountId/preview-messages
preview_reconnect_linkedin_accountPreview reconnecting a LinkedIn account (dry-run)preview
#preview_reconnect_linkedin_account
Dry-run of re-authenticating a LinkedIn account. Explains the side-effects without doing anything. ALWAYS call this first, reconnecting changes the account id upstream and can orphan existing outreach history.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes |
reconnect_linkedin_accountReconnect a LinkedIn account
Re-authenticate a LinkedIn account whose session died. Creates a NEW account id upstream: past outreach stays attached to the old id, so per-account history can look empty afterwards and running campaigns may need re-pointing. Only call after the user reviewed preview_reconnect_linkedin_account and approved.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes |
remove_campaign_linkedin_accountRemove a LinkedIn account from a campaignnew
#remove_campaign_linkedin_account
Stop using an account for this campaign; its pending contacts move to the remaining accounts. Already-sent invites stay.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Campaign id (UUID) from list_campaigns |
account_id | string | yes |
Calls: DELETE /v1/campaigns/:id/linkedin/accounts/:accountId
takeover_linkedin_contactTake over a LinkedIn contactnew
Stop the AI from messaging this LinkedIn contact (outreach id from list_campaign_linkedin_outreach or list_linkedin_conversations); pending auto-replies are cancelled. Nothing is sent.
| Argument | Type | Req. | Description |
|---|---|---|---|
outreach_id | string | yes |
update_linkedin_account_settingsUpdate LinkedIn account settingsnew
#update_linkedin_account_settings
Change AI auto-replies, invite / follow-up pitch, CTA, signature, reply delays/limits for ONE LinkedIn account (applies to all campaigns and SDR processes using it). Send only changed fields. To switch AI replies off for a single campaign use update_campaign settings.linkedinAutoReplyDisabled. No credits.
| Argument | Type | Req. | Description |
|---|---|---|---|
account_id | string | yes | |
enabled | boolean | AI auto-replies on/off | |
tone | string | one of: formal, casual, friendly | |
custom_instructions | string | null | ||
product_pitch | string | null | ||
calendly_url | string | null | Booking link offered in replies | |
reply_delay_min_minutes | integer | min 0; max 1440 | |
reply_delay_max_minutes | integer | min 0; max 1440 | |
daily_reply_limit | integer | min 0; max 500 | |
working_hours | object | ||
working_hours.start | integer | yes | min 0; max 23 |
working_hours.end | integer | yes | min 1; max 24 |
timezone | string | ||
agent_id | string | null | ||
contact_info | string | null | ||
invites_enabled | boolean | ||
invite_tone | string | one of: formal, casual, friendly | |
invite_pitch | string | null | ||
invite_custom_instructions | string | null | ||
invite_cta | string | null | ||
followup_enabled | boolean | ||
followup_pitch | string | null | ||
followup_custom_instructions | string | null | ||
followup_cta | string | null | ||
signature_enabled | boolean | ||
signature_text | string | null | ||
signature_mode | string | one of: first_message, every_message |
Mailboxes & senders 6
connect_client_mailboxConnect an own mailbox (SMTP/IMAP app password)newexecute
Connect the user's OWN mailbox for sending. Requires SMTP/IMAP hosts+ports and an APP PASSWORD, which passes through this conversation: prefer asking the user to connect it in the app (Settings › Email Sending) unless they explicitly give you the password. WARM-UP STARTS IMMEDIATELY: the real mailbox sends and receives warm-up mail. The password is never returned. Gmail: smtp.gmail.com:465 / imap.gmail.com:993; Outlook: smtp.office365.com:587 / outlook.office365.com:993.
| Argument | Type | Req. | Description |
|---|---|---|---|
email | string | yes | |
from_name | string | ||
provider | string | one of: google, microsoft, other | |
smtp_host | string | yes | |
smtp_port | integer | yes | |
imap_host | string | yes | |
imap_port | integer | yes | |
password | string | yes | App password (never stored by Vendo; passed to the sending operator) |
Calls: POST /v1/email/mailboxes
disconnect_client_mailboxDisconnect an own mailboxnewexecute
Stop using the mailbox as a sender. If none remain, client_owned campaigns cannot send or launch. Only after the user approved preview_disconnect_client_mailbox.
| Argument | Type | Req. | Description |
|---|---|---|---|
mailbox_id | string | yes |
list_client_mailboxesOwn sending mailboxesnew
The workspace's own connected mailboxes (status connecting|warming|ready|error|disconnected, warm-up reputation, last error). Capacity ≈ 50 emails/day per READY box. Used when settings.emailSenderMode=client_owned (classic campaigns and SDR). Read-only.
No arguments.
Calls: GET /v1/email/mailboxes
list_sender_emailsEmail senders + capacitynew
Sending addresses available to this workspace (Vendo managed pool + own mailboxes) with warm-up level, daily limit and status; summary.emails_per_day_ready = capacity in managed mode. Managed senders require a paid plan to launch (summary.paid_plan). Use ids in settings.senderEmailIds. Read-only.
No arguments.
Calls: GET /v1/email/senders
preview_disconnect_client_mailboxPreview disconnecting an own mailboxnewpreview
#preview_disconnect_client_mailbox
Dry-run: which client_owned campaigns are affected and how many usable mailboxes remain. Nothing changes.
| Argument | Type | Req. | Description |
|---|---|---|---|
mailbox_id | string | yes |
reconnect_client_mailboxReconnect an own mailboxnew
Retry the SMTP/IMAP connection of a mailbox in status error and refresh its status. No credits.
| Argument | Type | Req. | Description |
|---|---|---|---|
mailbox_id | string | yes |
Phone campaigns 4
activate_campaign_phoneActivate AI phone calls (REAL calls, credits per minute)newexecute
Schedule REAL outbound AI calls to every audience contact with a phone number; credits are charged per call minute. Same args as preview_activate_campaign_phone; only after the user approved that preview. Pause with pause_channel channel=phone_call.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Campaign id (UUID) from list_campaigns |
phone_number_ids | string[] | yes | |
call_objective | string | one of: sell, inform, book_meeting, collect_info, qualify_lead | |
talking_points | string | ||
max_calls_per_day | integer | min 1; max 1000 | |
max_call_duration_seconds | integer | min 30; max 1800 | |
working_hours_start | integer | min 0; max 23 | |
working_hours_end | integer | min 1; max 24 | |
timezone | string | ||
exclude_weekends | boolean |
get_campaign_phone_statsPhone stats of a campaignnew
Call counters: total, completed, active, pending, no answer, voicemail, failed, interested, meetings booked, not interested, average/total duration. Read-only.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Campaign id (UUID) from list_campaigns |
list_campaign_phone_callsCalls of a campaignnew
Calls with contact, status, outcome, duration, sentiment, summary and recording URL. Read-only.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Campaign id (UUID) from list_campaigns |
status | string | one of: pending, scheduled, calling, completed, failed, skipped, no_answer, voicemail, busy | |
limit | integer | Page size (default 50) min 1; max 200 | |
offset | integer | Rows to skip (default 0) min 0 |
preview_activate_campaign_phonePreview AI phone calls for a campaignnewpreview
#preview_activate_campaign_phone
Dry-run of activating the phone channel with numbers the account already owns: numbers, objective, calls/day, window and estimated credits per call/day. Nothing is scheduled. Numbers cannot be bought through the API.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Campaign id (UUID) from list_campaigns |
phone_number_ids | string[] | yes | Ids of the account's existing phone numbers |
call_objective | string | Default inform one of: sell, inform, book_meeting, collect_info, qualify_lead | |
talking_points | string | ||
max_calls_per_day | integer | Default 50 min 1; max 1000 | |
max_call_duration_seconds | integer | Default 300 min 30; max 1800 | |
working_hours_start | integer | Default: campaign working hours min 0; max 23 | |
working_hours_end | integer | min 1; max 24 | |
timezone | string | ||
exclude_weekends | boolean |
Monitor, AI lead discovery & live stats 9
get_campaign_activityCampaign activity feednew
Paged activity events of a campaign (discovery, outreach, engagement, system, billing). Poll it instead of a live stream. Read-only.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Campaign id (UUID) from list_campaigns |
category | string | one of: discovery, outreach, engagement, system, billing | |
limit | integer | Page size (default 50) min 1; max 200 | |
offset | integer | Rows to skip (default 0) min 0 |
get_campaign_discoveryGet AI lead discovery confignew
AI lead discovery settings of a campaign (null when never configured). Read-only.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Campaign id (UUID) from list_campaigns |
get_campaign_email_statsCampaign email funnel incl. deliverynew
Email funnel: sent, delivered, opened, clicked, replied (real / auto-reply / bounce), bounced; delivery block (delivered vs not delivered: bounced, rejected as spam, unsubscribed; sender inbox-placement test, real inbox vs spam folder is not reported by providers) and unique opened/clicked/replied. Read-only.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Campaign id (UUID) from list_campaigns |
get_campaign_live_statusCampaign email progressnew
Email channel progress: audience size, processed, % complete, event counts, queue depth and provider status / sent today. Read-only.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Campaign id (UUID) from list_campaigns |
get_campaign_monitorCampaign monitornew
Monitor page data: AI lead discovery config + stats, audience growth by day, the last 100 activity events, event counts and credits (balance, spent today/total, daily budget). Read-only.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Campaign id (UUID) from list_campaigns |
get_campaign_pulseCampaign pulse (live status)new
What the campaign is doing now (sending / idle / outside_hours / paused / queue_empty / done), today vs daily limits, 7–28-day email + LinkedIn series and the latest events. Read-only.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Campaign id (UUID) from list_campaigns |
days | integer | min 7; max 28 | |
limit | integer | Recent events to return min 8; max 60 |
Calls: GET /v1/campaigns/:id/pulse
list_campaign_discovered_leadsLeads found by AI discoverynew
#list_campaign_discovered_leads
Leads found by AI lead discovery for a campaign, filterable by status (discovered, enriching, enriched, added_to_audience, rejected, duplicate, enrichment_failed). Read-only.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Campaign id (UUID) from list_campaigns |
status | string | one of: discovered, enriching, enriched, added_to_audience, rejected, duplicate, enrichment_failed | |
limit | integer | Page size (default 50) min 1; max 200 | |
offset | integer | Rows to skip (default 0) min 0 |
preview_update_campaign_discoveryPreview AI lead discovery settingsnewpreview
#preview_update_campaign_discovery
Dry-run of enabling/changing automatic lead discovery: source, daily credit budget, leads/day. Nothing changes. Call before update_campaign_discovery whenever discovery is (or stays) enabled.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Campaign id (UUID) from list_campaigns |
enabled | boolean | ||
search_source | string | one of: apollo, signal | |
apollo_search_config | object | ||
apollo_search_config.person_titles | string[] | ||
apollo_search_config.organization_keywords | string[] | ||
apollo_search_config.person_locations | string[] | ||
signal_search_config | object | ||
signal_search_config.queries | string[] | ||
signal_search_config.linkedin_account_id | string | ||
daily_discovery_budget_credits | integer | min 0 | |
max_leads_per_day | integer | min 0 | |
max_total_discovered | integer | min 0 | |
search_interval_minutes | integer | min 5 | |
require_email | boolean |
update_campaign_discoveryEnable / change AI lead discovery (SPENDS CREDITS)newexecute
Turn on or adjust automatic lead discovery: it runs daily, SPENDS CREDITS up to daily_discovery_budget_credits and adds new leads to the campaign audience. Same args as preview_update_campaign_discovery; only after the user approved that preview. Disabling (enabled=false) needs no preview.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Campaign id (UUID) from list_campaigns |
enabled | boolean | ||
search_source | string | one of: apollo, signal | |
apollo_search_config | object | ||
apollo_search_config.person_titles | string[] | ||
apollo_search_config.organization_keywords | string[] | ||
apollo_search_config.person_locations | string[] | ||
signal_search_config | object | ||
signal_search_config.queries | string[] | ||
signal_search_config.linkedin_account_id | string | ||
daily_discovery_budget_credits | integer | min 0 | |
max_leads_per_day | integer | min 0 | |
max_total_discovered | integer | min 0 | |
search_interval_minutes | integer | min 5 | |
require_email | boolean |
Content (social) accounts 4
connect_content_accountGet a link to connect a social publishing accountnew
Returns a hosted URL a HUMAN must open in a browser to authorise a LinkedIn or Instagram publishing account. Give the link to the user; nothing is connected until they finish.
| Argument | Type | Req. | Description |
|---|---|---|---|
provider | string | yes | one of: LINKEDIN, INSTAGRAM |
redirect_path | string | App path to return to, default /settings?tab=social |
disconnect_content_accountDisconnect a social publishing accountnewexecute
Remove the publishing account; it can no longer publish posts. Only after the user approved preview_disconnect_content_account.
| Argument | Type | Req. | Description |
|---|---|---|---|
account_id | string | yes |
list_content_accountsSocial publishing accountsnew
LinkedIn / Instagram accounts connected for publishing posts (Settings › Social), with status. Read-only.
No arguments.
Calls: GET /v1/content/accounts
preview_disconnect_content_accountPreview disconnecting a social publishing accountnewpreview
#preview_disconnect_content_account
Dry-run of removing a publishing account. Nothing changes.
| Argument | Type | Req. | Description |
|---|---|---|---|
account_id | string | yes |
Audiences tools 31
People search & saved audiences 4
get_audienceGet one saved audience
Read one saved audience with its filters and contact count. Read-only, free.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes |
Calls: GET /v1/audiences/:id
list_audiencesList saved audiencesupdated
List the CAMPAIGN audiences (filter-based, attachable to campaigns) of this workspace. Uploaded / purchased / CRM-built lists ("My Audiences") are listed by list_custom_audiences. Read-only, free.
No arguments.
Calls: GET /v1/audiences
save_audienceSave an audienceupdated
Save a campaign audience from the same filter set as search_audience (call that first to check the counts). Returns the audience id to pass to create_campaign. No outreach happens here, no credits.
| Argument | Type | Req. | Description |
|---|---|---|---|
name | string | Audience name (auto-generated when omitted) | |
filters | object | yes | |
filters.countries | string[] | Country names, e.g. ["United Kingdom","Germany"] | |
filters.country | string | ||
filters.excludeCountries | string[] | ||
filters.cities | string[] | ||
filters.city | string | ||
filters.excludedCities | string[] | ||
filters.industries | string[] | ||
filters.excludedIndustries | string[] | ||
filters.positions | string[] | Job titles / roles, e.g. ["CEO","Head of Sales"] | |
filters.position | string | ||
filters.excludedPositions | string[] | ||
filters.companySizes | string[] | items: startup, small, medium, large, enterprise | |
filters.companySize | string | one of: startup, small, medium, large, enterprise | |
filters.excludedCompanySizes | string[] | items: startup, small, medium, large, enterprise | |
filters.tags | string[] | ||
filters.companyKeywords | string[] | Keywords matched in company description / name / position | |
filters.language | string | Approximate language segment by country set, e.g. "russian" | |
filters.maxAgeDays | integer | Only people added within the last N days min 1 | |
filters.mustHaveEmail | boolean | ||
filters.mustHaveLinkedin | boolean | ||
filters.mustHavePhone | boolean | ||
filters.mustHaveBookingUrl | boolean | ||
filters.searchMode | string | one of: keyword, exact, semantic | |
filters.semanticQuery | string | Free-text description of the ideal person (searchMode=semantic) | |
filters.exactKeyword | string | Exact phrase (searchMode=exact) |
Calls: POST /v1/audiences
search_audienceSearch the people databaseupdated
Search the people graph for prospects matching include AND exclude filters (countries, cities, industries, positions, company sizes, tags, company keywords, language, freshness, must-have-email/LinkedIn/phone/booking link). PREVIEW only, returns aggregate counts and a small sample. Does not save anything, does not contact anyone, does not spend credits. Next: save_audience with the same filters.
| Argument | Type | Req. | Description |
|---|---|---|---|
countries | string[] | Country names, e.g. ["United Kingdom","Germany"] | |
country | string | ||
excludeCountries | string[] | ||
cities | string[] | ||
city | string | ||
excludedCities | string[] | ||
industries | string[] | ||
excludedIndustries | string[] | ||
positions | string[] | Job titles / roles, e.g. ["CEO","Head of Sales"] | |
position | string | ||
excludedPositions | string[] | ||
companySizes | string[] | items: startup, small, medium, large, enterprise | |
companySize | string | one of: startup, small, medium, large, enterprise | |
excludedCompanySizes | string[] | items: startup, small, medium, large, enterprise | |
tags | string[] | ||
companyKeywords | string[] | Keywords matched in company description / name / position | |
language | string | Approximate language segment by country set, e.g. "russian" | |
maxAgeDays | integer | Only people added within the last N days min 1 | |
mustHaveEmail | boolean | ||
mustHaveLinkedin | boolean | ||
mustHavePhone | boolean | ||
mustHaveBookingUrl | boolean | ||
searchMode | string | one of: keyword, exact, semantic | |
semanticQuery | string | Free-text description of the ideal person (searchMode=semantic) | |
exactKeyword | string | Exact phrase (searchMode=exact) | |
limit | integer | Sample size (max 100) min 1; max 100 |
Calls: POST /v1/audiences/search
Custom audiences (My Audiences) 19
add_custom_audience_contactsAdd contacts to a custom audiencenew
Append up to 5000 contacts to a custom audience. Deduplicated inside the audience by email, LinkedIn URL or phone (duplicates and contacts with no email/LinkedIn/phone are skipped). Returns added / skipped / total_contacts. No credits.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Custom audience id (UUID) |
contacts | object[] | yes | min items 1; max items 5000 |
contacts[].first_name | string | ||
contacts[].last_name | string | ||
contacts[].email | string | ||
contacts[].phone | string | ||
contacts[].linkedin_url | string | ||
contacts[].company_name | string | ||
contacts[].position | string |
create_custom_audienceCreate an empty custom audiencenew
Create an empty, named custom audience (status ready), then fill it with add_custom_audience_contacts. To create one from a file use upload_custom_audience. No credits.
| Argument | Type | Req. | Description |
|---|---|---|---|
name | string | yes | min length 1; max length 255 |
Calls: POST /v1/audiences/custom
delete_custom_audienceDelete a custom audiencenewexecute
Permanently delete a custom audience with all its contacts and source file. NOT reversible. Only call after the user approved preview_delete_custom_audience.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Custom audience id (UUID) |
delete_custom_audience_contactRemove a contact from a custom audiencenewexecute
#delete_custom_audience_contact
Permanently remove one contact from a custom audience (not reversible). Optional reason (UI choices: Not relevant, Wrong contact, Already a customer, Bad data, Other) is logged to improve automatic selection. Only call after the user approved preview_delete_custom_audience_contact.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Custom audience id (UUID) |
contact_id | string | yes | Contact id (UUID) |
reason | string | max length 200 |
enrich_custom_audienceEnrich a custom audience (SPENDS CREDITS)newexecute
SPENDS CREDITS: starts background enrichment (email, phone, LinkedIn, WhatsApp lookups) of the not-yet-enriched contacts, 1 credit per contact actually enriched, charged as it runs. Only call after the user approved preview_enrich_custom_audience. Then poll get_custom_audience_enrichment; stop with stop_custom_audience_enrichment.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Custom audience id (UUID) |
export_custom_audience_csvExport a custom audience as CSVnew
Download every contact of a custom audience as CSV text (filename, total_rows, csv). Can be large. Read-only, free.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Custom audience id (UUID) |
get_custom_audienceGet one custom audiencenew
One custom audience: status, counts (with email/phone/LinkedIn), enrichment progress, column mapping and the first 20 contacts. Page through all contacts with get_custom_audience_contacts. Read-only, free.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Custom audience id (UUID) |
Calls: GET /v1/audiences/custom/:id
get_custom_audience_contactsPage through custom audience contactsnew
Paged contacts of a custom audience (max 100 per page). Contacts added by Vendo SDR carry signal_context (why they were picked: reasoning, relevance score, ICP group). Read-only, free.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Custom audience id (UUID) |
page | integer | min 1 | |
limit | integer | min 1; max 100 |
get_custom_audience_enrichmentEnrichment progress of a custom audiencenew
#get_custom_audience_enrichment
Status and progress of a running (or finished) enrichment: status, enrichment_total, enrichment_done, enrichment_progress (%), done flag. Poll every 10–30 s. Read-only.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Custom audience id (UUID) |
list_custom_audiencesList My Audiences (custom lists)new
List the workspace's custom audiences, uploaded CSV/XLSX lists, purchased audience sets, CRM selections, with status (processing|ready|enriching|error), contact counts and enrichment progress. These are different from campaign audiences (list_audiences). Read-only, free.
| Argument | Type | Req. | Description |
|---|---|---|---|
page | integer | min 1 | |
limit | integer | Omit to get all min 1; max 100 |
Calls: GET /v1/audiences/custom
prepare_campaign_audience_from_customUse a custom audience in a campaignnew
#prepare_campaign_audience_from_custom
Turn a READY custom audience into a campaign audience and return its id ({ id, name, stats }), pass it as audienceId to create_campaign. Sends nothing, no credits. The "Launch campaign" button of My Audiences.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Custom audience id (UUID) |
name | string | Campaign audience name (default: the custom audience name) max length 255 |
preview_custom_audience_fileDetect columns of a contacts filenewexecute
Step 1 of an upload: send a CSV/XLSX/XLS/TSV/TXT file (base64 or https URL) and get the detected headers, a suggested column mapping, sample rows, total rows and the list of target fields. Creates nothing, no credits. Then call upload_custom_audience (optionally with a corrected column_mapping).
| Argument | Type | Req. | Description |
|---|---|---|---|
filename | string | File name WITH extension (decides the type). Required with content_base64. | |
content_base64 | string | File content, base64 (≤ ~7 MB). Use this OR file_url. | |
file_url | string | Public https URL to fetch the file from (≤10 MB). Use this OR content_base64. |
preview_delete_custom_audiencePreview deleting a custom audience (dry-run)newpreview
#preview_delete_custom_audience
Dry-run: shows the audience name and how many contacts would be deleted. Changes nothing. ALWAYS call before delete_custom_audience and show the result.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Custom audience id (UUID) |
preview_delete_custom_audience_contactPreview removing a contact (dry-run)newpreview
#preview_delete_custom_audience_contact
Dry-run of removing one contact from a custom audience. Changes nothing. Show the result to the human before delete_custom_audience_contact.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Custom audience id (UUID) |
contact_id | string | yes | Contact id (UUID) |
reason | string |
preview_enrich_custom_audiencePreview enriching a custom audience (cost)newpreview
#preview_enrich_custom_audience
Dry-run of enrichment: how many contacts still need enrichment, the maximum credits (1 per contact), estimated minutes, the current balance and whether it can start. Spends nothing. ALWAYS call first and show the cost to the human.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Custom audience id (UUID) |
rename_custom_audienceRename a custom audiencenew
Rename a custom audience. Reversible, no credits.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Custom audience id (UUID) |
name | string | yes | min length 1; max length 255 |
stop_custom_audience_enrichmentStop a running enrichmentnew
#stop_custom_audience_enrichment
Stop an in-progress enrichment after the current contact (stops further spending). Already-enriched contacts stay enriched and charged.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Custom audience id (UUID) |
update_custom_audience_contactEdit a contact in a custom audiencenew
#update_custom_audience_contact
Edit one contact of a custom audience (only the fields you pass change). No credits.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Custom audience id (UUID) |
contact_id | string | yes | Contact id (UUID) |
first_name | string | ||
last_name | string | ||
full_name | string | ||
email | string | ||
phone | string | ||
linkedin_url | string | ||
company_name | string | ||
position | string | ||
country | string | ||
city | string | ||
website | string | ||
whatsapp | string | ||
telegram | string | ||
facebook_url | string | ||
instagram_url | string |
upload_custom_audienceCreate a custom audience from a filenew
Create a custom audience from a CSV/XLSX/XLS/TSV/TXT file given as base64 or an https URL. Parsing runs in the background: poll get_custom_audience until status is "ready". No credits (enrichment is a separate, paid step).
| Argument | Type | Req. | Description |
|---|---|---|---|
filename | string | File name WITH extension (decides the type). Required with content_base64. | |
content_base64 | string | File content, base64 (≤ ~7 MB). Use this OR file_url. | |
file_url | string | Public https URL to fetch the file from (≤10 MB). Use this OR content_base64. | |
name | string | Audience name (default: file name) max length 255 | |
column_mapping | object | { "<column index>": "<field>" } from preview_custom_audience_file (suggestedMapping / fieldOptions). Omit for auto-detect. |
Audience Sets marketplace 8
buy_audience_setBuy an audience set
SPENDS CREDITS (2 per contact). Purchases the set and snapshots its contacts into the workspace. Only call after the user reviewed preview_buy_audience_set output and explicitly approved the cost.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes |
get_audience_setGet one audience set
Details of one marketplace set by slug: size, description, freshness. Use the contact count here to work out the purchase cost (2 credits per contact). Read-only, free.
| Argument | Type | Req. | Description |
|---|---|---|---|
slug | string | yes | Human-readable slug, not a UUID |
Calls: GET /v1/audiences/sets/:slug
get_audience_set_contactOne contact card of an audience setnew
Full card of one contact in a marketplace set: person, company, buying signal, channels. Contact details stay masked until the set is bought. Contact ids come from get_audience_set_contacts. Read-only, free.
| Argument | Type | Req. | Description |
|---|---|---|---|
slug | string | yes | Set slug |
contact_id | string | yes | Contact id from get_audience_set_contacts |
get_audience_set_contactsPreview contacts in an audience set
Paginated preview of the contacts inside a marketplace set. Contact details stay masked until the set is purchased. Read-only, free.
| Argument | Type | Req. | Description |
|---|---|---|---|
slug | string | yes | |
offset | number | ||
limit | number |
launch_audience_setTurn an audience set into a campaign audienceupdatedexecute
Creates a campaign-ready audience from a marketplace set and returns its audienceId. Signals sets SPEND CREDITS (a fresh snapshot is bought, price per contact; it also appears in My Audiences); people sets are free. Does NOT contact anyone. Next: create_campaign with the audienceId, then preview_launch_campaign. Only call after the user approved preview_launch_audience_set.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes |
list_audience_setsBrowse the Audience Sets marketplace
Browse pre-built, ready-to-use audiences available for purchase, plus their categories. Browsing is free; buying costs credits. Read-only.
No arguments.
Calls: GET /v1/audiences/sets
preview_buy_audience_setPreview buying an audience set (dry-run)updatedpreview
Dry-run of purchasing an audience set, returns the set, contact count, price per contact, estimated credits, current balance and can_afford, WITHOUT spending credits. ALWAYS call this first and show the cost to the human.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Set id (UUID), not the slug |
preview_launch_audience_setPreview preparing a campaign audience from a set (dry-run)updatedpreview
Dry-run of launch_audience_set: shows the set, contact count, credits it would cost (signals sets: price per contact for a fresh snapshot; people sets: free) and the balance. Nothing is bought or sent. ALWAYS call this first and show the cost.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes |
CRM tools 53
Leads 18
crm_add_commentAdd an internal comment on a leadupdated
Internal team note on the lead card (not visible to the lead). Mentioned users (ids from crm_list_workspace_members) get a CRM notification.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | |
body | string | yes | min length 1; max length 8000 |
mentions | string[] | User ids to @mention |
crm_add_lead_to_branchAdd a lead to another pipelinenew
Place the lead in another pipeline too (entry stage); it stays in its current ones. Reversible via crm_move_lead_to_branch.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Lead id (UUID) |
branch_id | string | yes | "general" or a branch id from crm_list_all_branches |
assignee_user_id | string | Owner in that pipeline (default: current owner) |
crm_create_leadCreate a lead manuallyupdated
Add a lead card to the entry stage of the chosen pipeline(s). ALL fields optional, provide whatever is known (at least one of name/company/title/email/linkedin/phone). Not deduplicated (use crm_import_leads or crm_promote_contact_to_lead for dedup). Side-effect: a card appears on the customer board.
| Argument | Type | Req. | Description |
|---|---|---|---|
first_name | string | ||
last_name | string | ||
company_name | string | ||
title | string | ||
email | string | ||
linkedin_url | string | Full http(s) URL | |
phone | string | ||
avatar_url | string | Full http(s) URL | |
instagram_url | string | Full http(s) URL | |
facebook_url | string | Full http(s) URL | |
assigned_user_id | string | Owner, user id from crm_list_workspace_members (default: you) | |
notes | string | max length 8000 | |
branch_ids | string[] | Pipelines to place the lead in: "general" and/or branch ids (default General) |
Calls: POST /v1/crm/leads
crm_enrich_from_linkedinEnrich a person from a LinkedIn URL
Fetch name / title / current employer / avatar / location for a linkedin.com/in/... URL (same lookup the SDR hunters use). May also suggest an email as an UNVERIFIED pattern guess (email_confidence=pattern_guess). Costs one profile view from the workspace's shared daily LinkedIn budget, do not loop over big lists.
| Argument | Type | Req. | Description |
|---|---|---|---|
linkedin_url | string | yes |
Calls: POST /v1/crm/leads/enrich
crm_get_conversationGet lead conversation thread
The full message thread with this lead on their source channel (LinkedIn / email / widget / telegram / phone transcript). Read-only.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes |
crm_get_leadGet one CRM lead (full detail)
Full lead card: identity, source ("why is this person here"), company enrichment, activities, comments, assignments. Read-only.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes |
Calls: GET /v1/crm/leads/:id
crm_get_lead_branchesPipelines a lead is innew
Every pipeline a lead currently sits in, with its stage and owner per pipeline. Read-only.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Lead id (UUID) |
crm_import_leadsImport leads into the CRMnew
Import up to 1000 parsed rows as leads into the chosen pipelines (entry stage). Dedup by email/phone: an existing lead only gets its blank fields filled (never overwritten) and is added to the pipelines. Returns created / updated / skipped (+ reasons). No credits.
| Argument | Type | Req. | Description |
|---|---|---|---|
rows | object[] | yes | min items 1; max items 1000 |
rows[].first_name | string | ||
rows[].last_name | string | ||
rows[].email | string | ||
rows[].phone | string | ||
rows[].company | string | ||
rows[].title | string | ||
rows[].linkedin | string | LinkedIn URL | |
rows[].instagram | string | ||
rows[].facebook | string | ||
branch_ids | string[] | "general" and/or branch ids (default General) |
Calls: POST /v1/crm/leads/import
crm_list_leadsList CRM leadsupdated
List leads in the CRM pipeline. Filters: branch ("general" or a branch id from crm_list_branches; omitted = all branches visible to the user), stage (stage id), owner (user id from crm_list_workspace_members), source, q (name/company/email search), range or from/to, limit/offset. Read-only. Great first call for "who replied to me?".
| Argument | Type | Req. | Description |
|---|---|---|---|
branch | string | ||
stage | string | ||
owner | string | Assigned user id | |
source | string | one of: linkedin_outreach_reply, linkedin_inbound, linkedin_manual, email_reply_real, widget_chat, widget_voice, widget_form, narrator_form, telegram_message, whatsapp_message, phone_call_interested, meeting_booked, crm_integration_inbound, manual | |
q | string | ||
range | string | Omit for all time one of: 24h, 7d, 30d, 90d, 180d, 1y | |
from | string | ISO date, created from | |
to | string | ISO date, created to | |
limit | integer | min 1; max 200 | |
offset | integer | min 0 |
Calls: GET /v1/crm/leads
crm_move_leadMove lead to another stage
Drag-and-drop equivalent: move a lead to a different pipeline stage by stage_id (get ids from crm_list_stages). Reversible.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | |
stage_id | string | yes |
crm_move_lead_to_branchMove a lead between pipelinesnew
Move the lead out of one pipeline into another (entry stage, or to_stage_id of the destination). Reversible.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Lead id (UUID) |
from_branch_id | string | yes | "general" or the branch id it leaves |
to_branch_id | string | yes | "general" or the destination branch id |
to_stage_id | string | A stage of the destination (crm_list_stages branch=<to>) | |
assignee_user_id | string |
crm_preview_email_to_leadPreview an email to a lead (dry-run)newpreview
Dry-run of crm_send_email_to_lead: shows recipient, subject and body exactly as they would be sent. Sends nothing. ALWAYS call first and show the draft to the human.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Lead id (UUID) |
subject | string | yes | min length 1 |
body | string | yes | Plain-text message min length 1 |
to | string | Recipient (default: the lead's email) |
Calls: POST /v1/crm/leads/:id/email
crm_reply_to_leadSend a reply to a lead (REAL outbound message)
Sends a REAL LinkedIn message or email reply to the lead on their source channel, from the customer's own account. Also moves the lead to the "Manager Dialog" stage. ALWAYS show the drafted text to the human and get approval BEFORE calling this.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | |
text | string | yes | min length 1 |
Calls: POST /v1/crm/leads/:id/reply
crm_send_email_to_leadSend a REAL email to a leadnewexecute
Sends a REAL email from the CRM card ("<your name> via Vendo AI", replies go to your own address) and logs it on the card. Members need the crm-contact-details permission. Only call after the user approved the exact draft from crm_preview_email_to_lead.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Lead id (UUID) |
subject | string | yes | min length 1 |
body | string | yes | min length 1 |
to | string |
Calls: POST /v1/crm/leads/:id/email
crm_update_leadUpdate lead fieldsupdated
Patch lead attributes (name, company, title, email, LinkedIn, phone, notes, tags, deal value + currency). Stage and owner have their own tools (crm_move_lead, reassign_crm_lead). Reversible.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | |
first_name | string | ||
last_name | string | ||
company_name | string | ||
title | string | ||
email | string | ||
linkedin_url | string | Full http(s) URL | |
phone | string | ||
notes | string | ||
tags | string[] | Replaces the tag list | |
deal_value | number | ||
currency | string | ISO code, e.g. USD max length 3 |
Calls: PATCH /v1/crm/leads/:id
delete_crm_leadDelete a CRM lead (soft)
Soft-delete a lead, it disappears from the pipeline (restorable by support only). Only call after the user reviewed preview_delete_crm_lead output and explicitly approved.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes |
Calls: DELETE /v1/crm/leads/:id
preview_delete_crm_leadPreview lead deletion (dry-run)preview
Dry-run of deleting a CRM lead, shows who would be removed from the board. ALWAYS call this first and show the result to the human.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes |
Calls: DELETE /v1/crm/leads/:id
reassign_crm_leadReassign a CRM lead to another ownerupdated
Change who owns a lead (the new owner gets a CRM notification). Call crm_list_workspace_members first to get a valid user id, passing an unknown id fails. Write operation, no credit cost.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Lead id |
assignee_user_id | string | yes | New owner, a user id from crm_list_workspace_members |
reason | string | max length 255 |
Pipelines (branches) & stages 13
crm_create_branchCreate a CRM pipeline (branch)new
Create an isolated pipeline (team / direction / client) with its own default stages. member_user_ids = who can see it (General stays open to all). Workspace admins/owner only. No credits.
| Argument | Type | Req. | Description |
|---|---|---|---|
name | string | yes | min length 1; max length 120 |
member_user_ids | string[] | User ids from crm_list_workspace_members |
Calls: POST /v1/crm/branches
crm_create_stageCreate a pipeline stageupdated
Add a custom kanban column to a pipeline (name, optional hex color, is_terminal for Won/Lost-like end stages). Inserted before the terminal stages. branch = "general" (default) or a branch id.
| Argument | Type | Req. | Description |
|---|---|---|---|
name | string | yes | min length 1 |
color | string | #RRGGBB | |
is_terminal | boolean | ||
branch | string | "general" (default) or a branch id |
Calls: POST /v1/crm/pipeline-stages
crm_get_branchGet one CRM pipelinenew
One pipeline by id (id, name, is_default). Read-only.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Branch id (UUID) |
Calls: GET /v1/crm/branches/:id
crm_list_all_branchesList every CRM pipeline (for moves)new
Every pipeline of the workspace (id, name, is_default), wider than crm_list_branches for members; use it to pick a destination for crm_add_lead_to_branch / crm_move_lead_to_branch. Read-only.
No arguments.
Calls: GET /v1/crm/branches/all
crm_list_branchesList CRM branches (pipelines)
Branches are isolated pipelines inside one workspace (teams / directions / clients). Returns id, name, is_default ("General"), member_user_ids. Read-only. Call before crm_stats / crm_list_leads when the user has more than one branch, then pass branch=<id> (or "general").
No arguments.
Calls: GET /v1/crm/branches
crm_list_stagesList pipeline stagesupdated
Ordered kanban columns of ONE pipeline (id, name, color, is_terminal). Every branch has its own stages: pass branch ("general" = default, or a branch id from crm_list_branches). Read-only. Call before moving leads.
| Argument | Type | Req. | Description |
|---|---|---|---|
branch | string | "general" (default) or a branch id |
Calls: GET /v1/crm/pipeline-stages
crm_reorder_stagesReorder pipeline stagesnew
Set the full left-to-right order of one pipeline's stages (every stage id exactly once, get them from crm_list_stages with the same branch). Applies to the whole workspace. Reversible.
| Argument | Type | Req. | Description |
|---|---|---|---|
stage_ids | string[] | yes | All stage ids of the branch in the new order min items 1 |
branch | string | "general" (default) or a branch id |
crm_update_branchRename a pipeline / change its membersnew
Rename a pipeline and/or replace its member list. The General pipeline cannot be changed. Admins/owner only.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Branch id (UUID) |
name | string | min length 1; max length 120 | |
member_user_ids | string[] | Replaces the member list |
Calls: PATCH /v1/crm/branches/:id
crm_update_stageRename / recolor a pipeline stageupdated
Rename, recolor or toggle is_terminal of a stage. System stages (New, Manager Dialog) cannot be renamed. To change the column order use crm_reorder_stages.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | |
name | string | ||
color | string | #RRGGBB | |
is_terminal | boolean |
delete_crm_branchDelete a pipelinenewexecute
Delete a non-default pipeline; its leads are not deleted but return to General. NOT reversible. Admins/owner only. Only call after the user approved preview_delete_crm_branch.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Branch id (UUID) |
Calls: DELETE /v1/crm/branches/:id
delete_crm_stageDelete a pipeline stage
Delete an EMPTY, non-system pipeline stage. Only call after the user reviewed preview_delete_crm_stage and explicitly approved.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes |
preview_delete_crm_branchPreview deleting a pipeline (dry-run)newpreview
Dry-run: shows the pipeline and how many leads would fall back to General. Changes nothing. ALWAYS call before delete_crm_branch.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Branch id (UUID) |
Calls: DELETE /v1/crm/branches/:id
preview_delete_crm_stagePreview stage deletion (dry-run)preview
Dry-run of deleting a pipeline stage. Deletion is refused downstream if the stage still holds leads or is a system stage.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes |
Lead documents 5
crm_get_lead_documentDownload a lead documentnew
Returns the file as base64 with its name and mime type (files up to 5 MB; larger ones only via REST). Read-only.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Lead id (UUID) |
document_id | string | yes | Document id (UUID) |
crm_list_lead_documentsList documents on a leadnew
Files attached to a lead card (id, name, type, size, uploader, date). Read-only.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Lead id (UUID) |
crm_upload_lead_documentAttach a document to a leadnew
Attach one file to a lead card (base64 or https URL). Allowed: pdf, doc(x), xls(x), csv, txt, rtf, odt, ods, ppt(x), png, jpg, gif, webp, heic, zip. Stored privately; adds an activity entry. No credits.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Lead id (UUID) |
filename | string | File name WITH extension (decides the type). Required with content_base64. | |
content_base64 | string | File content, base64 (≤ ~7 MB). Use this OR file_url. | |
file_url | string | Public https URL to fetch the file from (≤10 MB). Use this OR content_base64. |
delete_crm_lead_documentDelete a lead documentnewexecute
Permanently delete a file from a lead card. NOT reversible. Only call after the user approved preview_delete_crm_lead_document.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Lead id (UUID) |
document_id | string | yes | Document id (UUID) |
preview_delete_crm_lead_documentPreview deleting a lead document (dry-run)newpreview
#preview_delete_crm_lead_document
Dry-run: shows which file would be deleted. Changes nothing.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Lead id (UUID) |
document_id | string | yes | Document id (UUID) |
Communications 4
crm_communication_cardsList communication cardsupdated
One card per campaign and per SDR search, with outbound counters. This is the FULL outbound stream, everyone contacted, whether or not they replied (CRM leads only contains people who answered). Contact emails/phones are masked for members without the "crm-contact-details" permission. Read-only, free.
| Argument | Type | Req. | Description |
|---|---|---|---|
limit | integer | min 1; max 500 |
crm_communication_historyOutbound history for one cardupdated
Every outbound touch for one campaign or SDR search, filterable by channel, reply status and date. Use crm_communication_cards first to get the id. Read-only, free.
| Argument | Type | Req. | Description |
|---|---|---|---|
type | string | yes | one of: campaign, sdr |
id | string | yes | Campaign id or SDR search id |
channel | string | one of: email, linkedin, phone | |
replied | boolean | ||
from | string | ISO date | |
to | string | ISO date | |
limit | number | ||
offset | number |
crm_communication_threadFull message thread for one contactupdated
The complete message thread with one contact inside a card. key comes from a crm_communication_history row (an email address or LinkedIn chat id). Read-only, free.
| Argument | Type | Req. | Description |
|---|---|---|---|
type | string | yes | one of: campaign, sdr |
id | string | yes | |
key | string | yes | Thread key from crm_communication_history |
crm_promote_contact_to_leadPromote a contacted person to a CRM leadnew
Turn a person from the Communications stream (crm_communication_history row) into a CRM lead in the New stage. Deduplicated: returns { lead_id, already_existed }. Needs at least one of email / linkedin_url / phone. No credits.
| Argument | Type | Req. | Description |
|---|---|---|---|
email | string | ||
linkedin_url | string | ||
phone | string | ||
contact_name | string | ||
company_name | string | ||
title | string | ||
avatar_url | string | ||
assigned_user_id | string | Owner (default: you) |
Calendar 4
crm_create_calendar_eventCreate a CRM calendar eventupdated
Add a custom event to the CRM planner. Write operation, no credit cost. Does NOT send invites to anyone, it only records the event on the internal calendar.
| Argument | Type | Req. | Description |
|---|---|---|---|
title | string | yes | |
start_at | string | yes | ISO datetime |
end_at | string | ISO datetime, after start_at (default start + 1 h) | |
all_day | boolean | ||
description | string | ||
color | string | #RRGGBB pattern ^#[0-9a-fA-F]{6}$ | |
lead_id | string | Link the event to a CRM lead |
Calls: POST /v1/crm/calendar/events
crm_delete_calendar_eventDelete a CRM calendar event
Remove a custom calendar event. Unlike deleting a lead or a stage this destroys no history and needs no preview step, but it cannot be undone.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes |
crm_list_calendar_eventsList CRM calendar events
List custom calendar events (meetings, reminders) in the CRM planner for a date range. Note: SDR sending windows also appear in the UI calendar but are derived from the search schedule, read those via sdr_get_search. Read-only, free.
| Argument | Type | Req. | Description |
|---|---|---|---|
from | string | ISO date, start of range | |
to | string | ISO date, end of range |
Calls: GET /v1/crm/calendar/events
crm_update_calendar_eventUpdate a CRM calendar eventupdated
Edit an existing custom calendar event (title, times, all-day, description, colour, linked lead, lead_id null unlinks). Write operation, no credit cost.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | |
title | string | ||
start_at | string | ISO datetime | |
end_at | string | ISO datetime | |
all_day | boolean | ||
description | string | ||
color | string | #RRGGBB pattern ^#[0-9a-fA-F]{6}$ | |
lead_id | string | null | Lead id, or null to unlink |
CRM notifications 3
crm_list_notificationsList CRM notificationsupdated
Latest 50 CRM notifications of the caller (types: assigned, reassigned, mentioned, new_lead, stage_change) plus total and unread counts. unread_only=true returns only unread ones. For the app bell (billing, meetings, invitations) use list_app_notifications. Read-only, free.
| Argument | Type | Req. | Description |
|---|---|---|---|
unread_only | boolean |
Calls: GET /v1/crm/notifications
crm_mark_all_notifications_readMark all notifications as read
#crm_mark_all_notifications_read
Clear the CRM notification badge by marking everything read.
No arguments.
crm_mark_notification_readMark one notification as read
Mark a single CRM notification as read. Write operation, no credit cost.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes |
Settings & lookups 6
crm_get_settingsGet CRM settings
Read CRM settings, including the notify_* toggles that control which events send an email/notification. Read-only, free.
No arguments.
Calls: GET /v1/crm/settings
crm_list_linkedin_accountsList connected LinkedIn accounts
List the LinkedIn accounts connected to this workspace, as shown in the CRM. Useful to see which account owns a conversation. Read-only, free.
No arguments.
crm_list_workspace_agentsList agents visible in CRM
List the AI agents in this workspace that CRM leads can be attributed to. Read-only, free.
No arguments.
Calls: GET /v1/crm/agents
crm_list_workspace_membersList workspace members
List the people in this workspace who can own a CRM lead. Use before reassign_crm_lead to get valid owner ids. Read-only, free.
No arguments.
crm_statsCRM pipeline stats
Counts per stage, new today/this week, stale leads. Read-only. Optional branch ("general" or a branch id from crm_list_branches), omitted = all branches visible to the user. If the workspace has more than one branch, call crm_list_branches first and report per branch or ask which one.
| Argument | Type | Req. | Description |
|---|---|---|---|
branch | string |
Calls: GET /v1/crm/stats
crm_update_settingsUpdate CRM settingsupdated
Change workspace CRM settings: which notifications fire (new lead, reassigned, mention; by email and/or in-app), the stale-lead alert, the default date range, and which agents / sources / LinkedIn accounts never create leads. Write operation, no credit cost. Turning notifications off or excluding sources means the human stops being alerted / leads stop appearing, so confirm with the user first. (Personal email preferences: update_notification_preferences.)
| Argument | Type | Req. | Description |
|---|---|---|---|
notify_new_lead | boolean | ||
notify_reassigned | boolean | ||
notify_mention | boolean | ||
notify_email | boolean | Send CRM notifications by email | |
notify_in_app | boolean | Show CRM notifications in the bell | |
stale_alert_days | integer | min 1; max 90 | |
default_date_range | string | one of: 24h, 7d, 30d, 90d, 180d, 1y, all | |
excluded_agent_ids | string[] | Agents whose conversations never create leads (ids from crm_list_workspace_agents). Replaces the list. | |
excluded_sources | string[] | Lead sources to ignore, e.g. ["widget_form"]. Replaces the list. | |
excluded_linkedin_account_ids | string[] | From crm_list_linkedin_accounts. Replaces the list. |
Calls: PATCH /v1/crm/settings
Workspace & account tools 34
Team 9
cancel_workspace_invitationCancel a pending invitationnew
Cancel a pending invitation (its link stops working). Admins only. Invitation ids come from get_workspace.pendingInvitations.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Invitation id (UUID) |
get_workspaceGet workspace, members and invitationsnew
Workspace name/owner, members (user_id, email, name, role, feature permissions), pending invitations, and whether the caller is admin/owner. Read-only.
No arguments.
Calls: GET /v1/workspace
invite_workspace_memberInvite a teammate (sends an email)newexecute
Sends a REAL invitation email (link valid 7 days). Admins only. Only call after the user approved preview_invite_workspace_member.
| Argument | Type | Req. | Description |
|---|---|---|---|
email | string | yes |
preview_invite_workspace_memberPreview inviting a teammate (dry-run)newpreview
#preview_invite_workspace_member
Dry-run: checks whether the email can be invited (caller is admin, not already a member, no pending invite). Sends nothing.
| Argument | Type | Req. | Description |
|---|---|---|---|
email | string | yes |
preview_remove_workspace_memberPreview removing a teammate (dry-run)newpreview
#preview_remove_workspace_member
Dry-run: shows who would lose access. Changes nothing. ALWAYS call before remove_workspace_member.
| Argument | Type | Req. | Description |
|---|---|---|---|
user_id | string | yes |
preview_set_workspace_member_rolePreview promoting/demoting an admin (dry-run)newpreview
#preview_set_workspace_member_role
Dry-run of set_workspace_member_role. Changes nothing.
| Argument | Type | Req. | Description |
|---|---|---|---|
user_id | string | yes | |
admin | boolean | yes |
remove_workspace_memberRemove a teammatenewexecute
Remove a member: they immediately lose access to this workspace (their account gets its own empty workspace). Re-adding needs a new invitation. Admins only. Only call after the user approved preview_remove_workspace_member.
| Argument | Type | Req. | Description |
|---|---|---|---|
user_id | string | yes |
set_workspace_member_permissionsSet a member's feature accessnew
#set_workspace_member_permissions
Restrict what a (non-admin) member sees: an allow-list of features, or null for full access. "crm-contact-details" controls whether they see lead emails/phones. Admins only; the owner and admins cannot be restricted.
| Argument | Type | Req. | Description |
|---|---|---|---|
user_id | string | yes | Member user_id from get_workspace |
permissions | string[] | null | yes | Allowed features, or null = full access items: dashboard, my-agents, sales-process, vendo-sdr, audiences, crm, analytics, billing, settings, crm-contact-details |
set_workspace_member_rolePromote / demote a workspace adminnewexecute
admin=true makes the member an admin (full access, can manage team and pipelines); false demotes to member. Owner only. Only call after the user approved preview_set_workspace_member_role.
| Argument | Type | Req. | Description |
|---|---|---|---|
user_id | string | yes | |
admin | boolean | yes |
Profile & preferences 5
get_notification_preferencesGet my notification preferencesnew
Personal email/in-app preferences: email_notifications, in_app_notifications, meeting_reminders, credit_alerts, analytics_emails_enabled. Read-only.
No arguments.
get_profileGet my profilenew
The API key owner's profile: name, email, company, position, avatar. Read-only.
No arguments.
Calls: GET /v1/me
set_profile_avatarSet my profile photonew
Upload a JPG/PNG/GIF (≤5 MB) as the profile photo, from an https URL or base64.
| Argument | Type | Req. | Description |
|---|---|---|---|
image_url | string | Public https image URL | |
content_base64 | string | ||
filename | string | e.g. me.png (required with content_base64) |
Calls: PATCH /v1/me/avatar
update_notification_preferencesUpdate my notification preferencesnew
#update_notification_preferences
Turn personal notifications on/off. Turning them off means the human stops being alerted (e.g. low credits, meetings), confirm with the user first.
| Argument | Type | Req. | Description |
|---|---|---|---|
email_notifications | boolean | ||
in_app_notifications | boolean | ||
meeting_reminders | boolean | ||
credit_alerts | boolean | ||
analytics_emails_enabled | boolean | Periodic analytics report emails |
update_profileUpdate my profilenew
Change first_name, last_name, company_name, position of the API key owner. (Email, password and 2FA are UI-only.)
| Argument | Type | Req. | Description |
|---|---|---|---|
first_name | string | min length 1; max length 100 | |
last_name | string | max length 100 | |
company_name | string | max length 200 | |
position | string | max length 200 |
Calls: PATCH /v1/me
In-app notifications 5
delete_app_notificationDelete an app notificationnew
Remove one in-app notification from the caller's list.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Notification id (UUID) |
Calls: DELETE /v1/notifications/:id
get_app_notifications_unread_countUnread app notifications countnew
#get_app_notifications_unread_count
Number of unread in-app notifications (the bell badge). Read-only.
No arguments.
list_app_notificationsList app notifications (bell)new
The caller's in-app notifications, newest first (type, subject, preview, isRead, createdAt), with unreadCount and paging. Types include MEETING_TRANSCRIPT, MEETING_NOTIFICATION, SUBSCRIPTION_*, PAYMENT_RECEIPT, LOW_CREDITS, CREDITS_ADDED, INVITATION, CUSTOM. CRM lead notifications are separate (crm_list_notifications). Read-only.
| Argument | Type | Req. | Description |
|---|---|---|---|
page | integer | min 1 | |
limit | integer | min 1; max 100 |
Calls: GET /v1/notifications
mark_all_app_notifications_readMark all app notifications readnew
#mark_all_app_notifications_read
Mark every in-app notification of the caller as read (clears the badge).
No arguments.
mark_app_notification_readMark an app notification readnew
Mark one in-app notification as read.
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Notification id (UUID) |
CRM integrations 10
connect_crm_integrationStart connecting Kommo / HubSpot (OAuth)new
Returns an authorization URL a HUMAN must open in a browser (logged in to Kommo/HubSpot) and approve within 20 minutes; the connection completes server-side. Give the URL to the user, then poll get_crm_integration. Kommo needs save_kommo_credentials first.
| Argument | Type | Req. | Description |
|---|---|---|---|
provider | string | yes | one of: kommo, hubspot |
disconnect_crm_integrationDisconnect a CRM integrationnewexecute
Remove the Kommo/HubSpot connection: syncing stops, tokens and settings are deleted; reconnecting needs OAuth again. Only call after the user approved preview_disconnect_crm_integration.
| Argument | Type | Req. | Description |
|---|---|---|---|
provider | string | yes | one of: kommo, hubspot |
get_crm_integrationGet one CRM integrationnew
Status and settings of the Kommo or HubSpot connection ({connected:false} if none). Use it to poll after connect_crm_integration. Read-only.
| Argument | Type | Req. | Description |
|---|---|---|---|
provider | string | yes | one of: kommo, hubspot |
get_crm_integration_logsCRM integration sync lognew
Recent sync events (contact/lead created, errors) of the caller's own Kommo/HubSpot connection. Read-only.
| Argument | Type | Req. | Description |
|---|---|---|---|
provider | string | yes | one of: kommo, hubspot |
limit | integer | min 1; max 200 |
list_crm_integrationsList CRM integrationsnew
External CRM connections of the workspace (Kommo, HubSpot): status, account, sync settings and counters. Tokens are never returned. Read-only.
No arguments.
Calls: GET /v1/integrations
list_kommo_pipelinesList Kommo pipelinesnew
Pipelines and their statuses (stages) in the connected Kommo account, values for pipeline_config. Read-only.
No arguments.
list_kommo_usersList Kommo usersnew
Users of the connected Kommo account, values for pipeline_config.responsible_user_id. Read-only.
No arguments.
preview_disconnect_crm_integrationPreview disconnecting a CRM (dry-run)newpreview
#preview_disconnect_crm_integration
Dry-run: shows which connection would be removed. Changes nothing.
| Argument | Type | Req. | Description |
|---|---|---|---|
provider | string | yes | one of: kommo, hubspot |
save_kommo_credentialsSave Kommo app credentialsnew
Kommo private integration step 1: store the Integration ID (UUID) and Secret key of the Vendo app the client created in their Kommo account. The secret is never echoed back. Returns the redirect_uri to paste into Kommo. Then connect_crm_integration provider=kommo.
| Argument | Type | Req. | Description |
|---|---|---|---|
client_id | string | yes | Kommo Integration ID (UUID) |
client_secret | string | yes | Kommo Secret key |
update_crm_integration_settingsUpdate CRM integration settingsnew
#update_crm_integration_settings
What to sync and where: sync_conversations, sync_contacts, create_leads, auto_sync, pipeline_config (Kommo pipeline_id / stage_id / responsible_user_id from list_kommo_pipelines / list_kommo_users), field_mapping. Affects what is written into the external CRM, confirm with the user.
| Argument | Type | Req. | Description |
|---|---|---|---|
provider | string | yes | one of: kommo, hubspot |
sync_conversations | boolean | ||
sync_contacts | boolean | ||
create_leads | boolean | ||
auto_sync | boolean | ||
pipeline_config | object | ||
pipeline_config.pipeline_id | string | ||
pipeline_config.stage_id | string | ||
pipeline_config.responsible_user_id | string | ||
field_mapping | object |
Referral program 5
create_referral_linkCreate a referral linknew
Create a new tracked referral link (max 20 active). Returns code and url.
| Argument | Type | Req. | Description |
|---|---|---|---|
label | string | Where you will share it, e.g. "LinkedIn post" max length 80 |
Calls: POST /v1/referral/links
get_referral_overviewReferral program overviewnew
Commission %, minimum payout, totals (visits, signups, paying customers, revenue, earned, paid out, available), every referral link with its stats, and payout request statuses. Payout requests themselves can only be made in the app. Read-only.
No arguments.
Calls: GET /v1/referral
list_referred_usersList referred usersnew
People who signed up through the caller's referral links (masked email, link, signup date, free/paying, revenue and commission). Read-only.
No arguments.
update_referral_linkRename / archive a referral linknew
Change a link label or archive it (archived links stop attributing new signups; archived=false restores).
| Argument | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Referral link id (UUID) |
label | string | max length 80 | |
archived | boolean |
Calls: PATCH /v1/referral/links/:id
Analytics & planner tools 4
Analytics 3
get_analytics_agentsAgents analytics (page /analytics/agents)new
All metrics of the workspace's agents for a UTC date range (default: the page default), optionally one agent: totals, rates, daily series, funnel (widget loads → opens → conversations → contacts → leads), by agent / channel, leads by source, recent leads, credits by type, languages, pages, traffic sources, hours, durations, previous-period comparison. Read-only.
| Argument | Type | Req. | Description |
|---|---|---|---|
from | string | YYYY-MM-DD | |
to | string | YYYY-MM-DD | |
agent_id | string |
Calls: GET /v1/analytics/agents
get_analytics_sdrSDR & outreach analytics (page /analytics/sdr)new
Outreach analytics for a UTC date range across Vendo SDR and classic campaigns, or narrowed to one SDR search (search_id) or one campaign (campaign_id), never both: totals, rates, daily series, delivery (sent/delivered/bounced/inbox placement), by product / source / type, contacts by source, enrichment, funnel, per-process rows. Read-only.
| Argument | Type | Req. | Description |
|---|---|---|---|
from | string | YYYY-MM-DD | |
to | string | YYYY-MM-DD | |
search_id | string | ||
campaign_id | string |
Calls: GET /v1/analytics/sdr
get_campaign_analyticsCampaign analytics (all campaigns)new
Sales Process KPIs for the workspace: totals (campaigns, emails sent, opens, clicks, replies, contacts, rates) and per-campaign performance, optionally for a date range. Read-only. For SDR + campaigns combined use the SDR analytics tool.
| Argument | Type | Req. | Description |
|---|---|---|---|
from | string | YYYY-MM-DD | |
to | string | YYYY-MM-DD |
Calls: GET /v1/analytics/campaigns
Planner 1
get_planner_eventsPlanner: sending windows per daynew
The planner layer of the calendar page: campaign and SDR sending windows per day and channel (paused flag, planned today, sent today) with campaign/search info, for an ISO date range (wide ranges are capped; range says what was materialised). CRM events: crm_list_calendar_events. Read-only.
| Argument | Type | Req. | Description |
|---|---|---|---|
from | string | yes | ISO date |
to | string | yes | ISO date |
Calls: GET /v1/planner/events
Billing (read-only) tools 14
Billing 14
get_balanceGet credit balanceupdated
Get the current shared credit balance of the workspace (credits belong to the workspace owner; member keys see the same number). Read-only, no side-effects, no cost.
No arguments.
Calls: GET /v1/billing/balance
get_balance_fullGet full billing balance + plan summary
Richer than get_balance: returns balance, total subscription credits, total purchased, total spent, free-trial credits, email/workspace info.
No arguments.
Calls: GET /v1/billing/balance/full
get_credit_transactionsGet credit transaction historyupdated
Credit deductions + grants of the workspace, newest first. Optional filters: type, date range (from/to, a date-only to is inclusive), pagination. With filters the gateway scans the newest 5000 rows; has_more/total_matched tell you if there is more. Read-only.
| Argument | Type | Req. | Description |
|---|---|---|---|
type | string | Transaction type, e.g. SUBSCRIPTION_GRANT, PURCHASE, FREE_TRIAL, BONUS, REFUND, EMAIL_OUTREACH, LINKEDIN_INVITE, LINKEDIN_MESSAGE, SDR_CONTACT, PHONE_CALL, VOICE_MINUTE, TEXT_MESSAGE, AUDIENCE_ENRICHMENT, AUDIENCE_SET_CONTACT, DISCOVERY_ENRICH, ASSISTANT_MESSAGE, SCRIPT_GENERATION, IMAGE_GENERATION | |
from | string | ISO date, e.g. 2026-06-01 | |
to | string | ISO date, e.g. 2026-06-30 | |
limit | integer | min 1; max 200 | |
offset | integer | min 0 |
Calls: GET /v1/billing/transactions
get_credit_usage_by_agentGet credit spend per agentupdated
Total credit spend per agent in the workspace. Identifies the most expensive agents.
No arguments.
get_credit_usage_by_typeGet credit spend by type / sessionupdated
Per-session credit spend (newest first) with connectionType (email_outreach, linkedin_invite, linkedin_message, phone_call, widget, voice, sdr_contact, ...). With from/to it also returns totals_by_type for that range. Read-only.
| Argument | Type | Req. | Description |
|---|---|---|---|
from | string | ISO date, start of range | |
to | string | ISO date, end of range (date-only = inclusive) | |
limit | integer | min 1; max 500 | |
offset | integer | min 0 |
get_credit_usage_for_agentGet credit spend for a single agent
Total credits spent by one specific agent.
| Argument | Type | Req. | Description |
|---|---|---|---|
agent_id | string | yes |
get_credit_usage_for_agent_connectionsGet credit spend by connection for an agent
#get_credit_usage_for_agent_connections
Per-channel credit spend (widget, telegram, phone, etc.) for one agent. Identifies the costliest channels.
| Argument | Type | Req. | Description |
|---|---|---|---|
agent_id | string | yes |
get_credit_usage_monthlyGet credit spend per monthupdated
Calendar-month breakdown of credit spend across all categories.
No arguments.
get_invoicesGet Stripe invoices
List of Stripe invoices (paid + open). Optional year/month scope.
| Argument | Type | Req. | Description |
|---|---|---|---|
year | integer | ||
month | integer | min 1; max 12 |
Calls: GET /v1/billing/invoices
get_pricingGet public pricing catalogueupdated
List of public plans + add-ons (Extra Agent Slot, SDR sender pair, etc.) with prices. Optional currency filter (USD/GBP/EUR); omitted = every currency. Read-only.
| Argument | Type | Req. | Description |
|---|---|---|---|
currency | string | Only prices in this currency one of: USD, GBP, EUR |
Calls: GET /v1/billing/pricing
get_sdr_email_capacityGet Vendo SDR managed-sender capacity
How many SDR sender email addresses the account has (included + purchased extras) and the estimated daily email throughput. Read-only.
No arguments.
get_subscriptionGet current subscription
Plan, status (ACTIVE/CANCELLED/PAST_DUE/EXPIRED/TRIALING), period dates, currency, amount, Stripe IDs.
No arguments.
Calls: GET /v1/billing/subscription
get_subscription_limitsGet plan limits (max agents, extra slots)updated
Base + purchased extra agent slots. Useful to check "can I create another agent?" before calling create_agent.
No arguments.
get_supported_currenciesSupported billing currenciesnew
Currencies Vendo bills in (code, symbol, name) and the account's own billing currency. Read-only.
No arguments.
Calls: GET /v1/billing/currencies
Guides
Complete, runnable flows. Each block works on its own: set VENDO_API_KEY (the curl versions use jq; JavaScript needs Node 18+ as an ES module; Python needs requests). Steps that spend credits or contact people are called out; try them with a vnd_test_… key first, writes are then refused, previews still work.
Create and launch an AI agent on a website widget
Create the agent, give it company details, generate the script (a job), set contact channels, activate it and add the WEBSITE_WIDGET channel. To read the company website automatically instead of writing the description, use POST /v1/agents/:id/deep-scan (15 credits, dry_run → confirm).
H=(-H "Authorization: Bearer $VENDO_API_KEY" -H "Content-Type: application/json")
API=https://api.vendo-ai.com/v1
# 1. Create the agent (starts in DRAFT, contacts nobody)
AGENT_ID=$(curl -s -X POST $API/agents "${H[@]}" \
-d '{"name":"Anna","description":"Answers questions about Acme CRM and books demos","purpose":"sales","gender":"female"}' \
| jq -r .data.id)
# 2. Company details the script is written from
curl -s -X PATCH $API/agents/$AGENT_ID/company "${H[@]}" -d '{
"company_name": "Acme", "company_website": "https://acme.com",
"company_description": "Acme builds a CRM for small sales teams. Plans from $29/month."}'
# 3. Generate the script (1 credit, 60-90 s). 202 → poll the job.
R=$(curl -s -X POST $API/agents/$AGENT_ID/generate-script "${H[@]}" -d '{"script_style":"FRIENDLY_CASUAL"}')
JOB=$(echo "$R" | jq -r .data.job_id)
while [ "$(echo "$R" | jq -r .data.status)" = "running" ]; do
sleep 15; R=$(curl -s $API/agents/$AGENT_ID/jobs/$JOB "${H[@]}")
done
# 4. Where visitors can book / reach you
curl -s -X PATCH $API/agents/$AGENT_ID/contact-channels "${H[@]}" \
-d '{"meeting_url":"https://calendly.com/acme/demo","email":"sales@acme.com"}'
# 5. Activate the agent and add the website widget channel
curl -s -X PATCH $API/agents/$AGENT_ID/status "${H[@]}" -d '{"status":"ACTIVE"}'
CID=$(curl -s -X POST $API/agents/$AGENT_ID/connections "${H[@]}" -d '{
"type":"WEBSITE_WIDGET","website_url":"acme.com",
"widget_config":{"teaser":true,"collectName":true,"collectEmail":true,
"texts":{"welcomeMessage":"Hi! I am Anna from Acme."}}}' | jq -r .data.id)
# 6. Embed code — paste before </body> on your site
curl -s $API/agents/$AGENT_ID/connections/$CID/embed-code "${H[@]}" | jq -r .data.embed_codeconst BASE = "https://api.vendo-ai.com/v1";
const KEY = process.env.VENDO_API_KEY;
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
async function vendo(method, path, body) {
const res = await fetch(BASE + path, {
method,
headers: { Authorization: `Bearer ${KEY}`, "Content-Type": "application/json" },
body: body ? JSON.stringify(body) : undefined,
});
const json = await res.json();
if (!res.ok) throw new Error(`${res.status} ${json.code}: ${json.message}`);
return json.data; // 202 (job still running) also lands here
}
const agent = await vendo("POST", "/agents", {
name: "Anna", description: "Answers questions about Acme CRM and books demos",
purpose: "sales", gender: "female",
});
await vendo("PATCH", `/agents/${agent.id}/company`, {
company_name: "Acme", company_website: "https://acme.com",
company_description: "Acme builds a CRM for small sales teams. Plans from $29/month.",
});
// Script generation is a job: 200 with the result, or 202 → poll
let job = await vendo("POST", `/agents/${agent.id}/generate-script`, { script_style: "FRIENDLY_CASUAL" });
while (job.status === "running") {
await sleep(15000);
job = await vendo("GET", `/agents/${agent.id}/jobs/${job.job_id ?? job.id}`);
}
if (job.status === "error") throw new Error(JSON.stringify(job.error));
await vendo("PATCH", `/agents/${agent.id}/contact-channels`, {
meeting_url: "https://calendly.com/acme/demo", email: "sales@acme.com",
});
await vendo("PATCH", `/agents/${agent.id}/status`, { status: "ACTIVE" });
const channel = await vendo("POST", `/agents/${agent.id}/connections`, {
type: "WEBSITE_WIDGET", website_url: "acme.com",
widget_config: { teaser: true, collectName: true, collectEmail: true,
texts: { welcomeMessage: "Hi! I am Anna from Acme." } },
});
const embed = await vendo("GET", `/agents/${agent.id}/connections/${channel.id}/embed-code`);
console.log(embed.embed_code); // paste before </body>import os, time, requests
BASE = "https://api.vendo-ai.com/v1"
S = requests.Session()
S.headers["Authorization"] = f"Bearer {os.environ['VENDO_API_KEY']}"
def vendo(method, path, body=None, **params):
r = S.request(method, BASE + path, json=body, params=params or None, timeout=260)
if r.status_code >= 400:
e = r.json()
raise RuntimeError(f"{r.status_code} {e.get('code')}: {e.get('message')}")
return r.json()["data"] # 202 (job still running) also lands here
agent = vendo("POST", "/agents", {
"name": "Anna", "description": "Answers questions about Acme CRM and books demos",
"purpose": "sales", "gender": "female"})
aid = agent["id"]
vendo("PATCH", f"/agents/{aid}/company", {
"company_name": "Acme", "company_website": "https://acme.com",
"company_description": "Acme builds a CRM for small sales teams. Plans from $29/month."})
job = vendo("POST", f"/agents/{aid}/generate-script", {"script_style": "FRIENDLY_CASUAL"})
while job["status"] == "running": # 202 → poll the job
time.sleep(15)
job = vendo("GET", f"/agents/{aid}/jobs/{job.get('job_id') or job['id']}")
assert job["status"] == "done", job
vendo("PATCH", f"/agents/{aid}/contact-channels",
{"meeting_url": "https://calendly.com/acme/demo", "email": "sales@acme.com"})
vendo("PATCH", f"/agents/{aid}/status", {"status": "ACTIVE"})
channel = vendo("POST", f"/agents/{aid}/connections", {
"type": "WEBSITE_WIDGET", "website_url": "acme.com",
"widget_config": {"teaser": True, "collectName": True, "collectEmail": True,
"texts": {"welcomeMessage": "Hi! I am Anna from Acme."}}})
print(vendo("GET", f"/agents/{aid}/connections/{channel['id']}/embed-code")["embed_code"])Put an agent on the phone with an existing number
Uses a number you already own (numbers are bought in the app). Calls cost 25 credits per minute; the test call dials a real phone and needs dry_run → confirm.
H=(-H "Authorization: Bearer $VENDO_API_KEY" -H "Content-Type: application/json")
API=https://api.vendo-ai.com/v1
AGENT_ID=your-agent-id # an agent with a generated script
# 1. Numbers you already own (buying numbers is done in the app)
NUMBER_ID=$(curl -s $API/phone/numbers "${H[@]}" | jq -r '.data[0].id')
# 2. Optional: draft a call script for an objective (free, not saved)
curl -s -X POST $API/agents/$AGENT_ID/phone/generate-script "${H[@]}" -d '{"call_objective":"book_meeting"}'
# 3. Set up the Voice Call channel (up to ~1 min); accept_incoming answers calls too
curl -s -X POST $API/agents/$AGENT_ID/phone/setup "${H[@]}" -d "{
\"phone_number_id\": \"$NUMBER_ID\", \"call_objective\": \"book_meeting\",
\"voice_gender\": \"female\", \"accept_incoming\": true }"
# 4. Real test call to your own phone — preview, then confirm (25 credits/min, max 2 min)
curl -s -X POST $API/agents/$AGENT_ID/phone/test-call "${H[@]}" -d '{"to_number":"+447700900123","dry_run":true}'
curl -s -X POST $API/agents/$AGENT_ID/phone/test-call "${H[@]}" -d '{"to_number":"+447700900123","confirm":true}'
# 5. Call log with outcomes and recordings
curl -s "$API/agents/$AGENT_ID/phone/calls" "${H[@]}"const BASE = "https://api.vendo-ai.com/v1";
const KEY = process.env.VENDO_API_KEY;
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
async function vendo(method, path, body) {
const res = await fetch(BASE + path, {
method,
headers: { Authorization: `Bearer ${KEY}`, "Content-Type": "application/json" },
body: body ? JSON.stringify(body) : undefined,
});
const json = await res.json();
if (!res.ok) throw new Error(`${res.status} ${json.code}: ${json.message}`);
return json.data; // 202 (job still running) also lands here
}
const agentId = process.env.AGENT_ID; // agent with a generated script
const numbers = await vendo("GET", "/phone/numbers");
if (!numbers.length) throw new Error("Buy a number in the app first");
await vendo("POST", `/agents/${agentId}/phone/setup`, {
phone_number_id: numbers[0].id,
call_objective: "book_meeting",
voice_gender: "female",
accept_incoming: true,
});
const preview = await vendo("POST", `/agents/${agentId}/phone/test-call`,
{ to_number: "+447700900123", dry_run: true });
console.log(preview); // show to a human first
await vendo("POST", `/agents/${agentId}/phone/test-call`,
{ to_number: "+447700900123", confirm: true }); // dials a real phone
console.log(await vendo("GET", `/agents/${agentId}/phone/calls`));import os, time, requests
BASE = "https://api.vendo-ai.com/v1"
S = requests.Session()
S.headers["Authorization"] = f"Bearer {os.environ['VENDO_API_KEY']}"
def vendo(method, path, body=None, **params):
r = S.request(method, BASE + path, json=body, params=params or None, timeout=260)
if r.status_code >= 400:
e = r.json()
raise RuntimeError(f"{r.status_code} {e.get('code')}: {e.get('message')}")
return r.json()["data"] # 202 (job still running) also lands here
aid = os.environ["AGENT_ID"] # agent with a generated script
numbers = vendo("GET", "/phone/numbers")
assert numbers, "Buy a number in the app first"
vendo("POST", f"/agents/{aid}/phone/setup", {
"phone_number_id": numbers[0]["id"], "call_objective": "book_meeting",
"voice_gender": "female", "accept_incoming": True})
print(vendo("POST", f"/agents/{aid}/phone/test-call",
{"to_number": "+447700900123", "dry_run": True})) # preview
vendo("POST", f"/agents/{aid}/phone/test-call",
{"to_number": "+447700900123", "confirm": True}) # real call
print(vendo("GET", f"/agents/{aid}/phone/calls"))Run Vendo SDR: company + website → first contacts → outreach
The same flow as the New SDR wizard. The research reuses an existing company profile for the domain when there is one; otherwise it builds a hidden one (deep scan 15 credits + playbook 1 credit) that never counts toward your agent limit. Contacts cost 5 credits each; outreach starts only after confirm.
H=(-H "Authorization: Bearer $VENDO_API_KEY" -H "Content-Type: application/json")
API=https://api.vendo-ai.com/v1
# 1. Company + website: preview cost/reuse, then start (deep scan 15 credits + playbook 1 credit
# when a new company profile is built; the ICP research itself is free)
curl -s -X POST $API/sdr/research "${H[@]}" -d '{"company_name":"Brightsmile Software","website":"brightsmile.io","dry_run":true}'
AGENT_ID=$(curl -s -X POST $API/sdr/research "${H[@]}" \
-d '{"company_name":"Brightsmile Software","website":"brightsmile.io","confirm":true}' | jq -r .data.agent_id)
# 2. Wait for the company profile, then for the research job
until [ "$(curl -s "$API/sdr/research?agent_id=$AGENT_ID" "${H[@]}" | jq -r .data.job.id)" != "null" ]; do sleep 10; done
JOB_ID=$(curl -s "$API/sdr/research?agent_id=$AGENT_ID" "${H[@]}" | jq -r .data.job.id)
until [[ "$(curl -s $API/sdr/research/$JOB_ID "${H[@]}" | jq -r .data.job.status)" =~ done|error ]]; do sleep 5; done
curl -s $API/sdr/research/$JOB_ID "${H[@]}" | jq .data.job.result.profile # review the ICP
# 3. Create & start the search from the research (all 24 hunters, first buyers copied in)
SEARCH_ID=$(curl -s -X POST $API/sdr/searches "${H[@]}" \
-d "{\"research_job_id\": \"$JOB_ID\", \"target_regions\": [\"GB\"]}" | jq -r .data.search.id)
# 4. Contacts that passed the ICP check (5 credits per usable contact)
curl -s "$API/sdr/people?search_id=$SEARCH_ID&group=fits&limit=50" "${H[@]}"
# 5. Messages, then launch outreach: preview, then confirm
curl -s -X PATCH $API/sdr/searches/$SEARCH_ID/outreach/content "${H[@]}" -d '{
"email": {"mode": "ai", "prompt": "Short, specific, one question.", "tone": "friendly"},
"linkedin": {"invite_enabled": true, "invite_mode": "ai",
"conversation_rules": "Goal: a 15-minute demo. Offer https://cal.com/brightsmile/demo."}}'
LAUNCH='{"channels":["email","linkedin"],"email_sender_mode":"client_owned","client_email_provider":"google",
"timezone":"Europe/London","work_start_hour":9,"work_end_hour":17,"exclude_weekends":true,"icp_segment":"fit"'
curl -s -X POST $API/sdr/searches/$SEARCH_ID/outreach/launch "${H[@]}" -d "$LAUNCH,\"dry_run\":true}"
curl -s -X POST $API/sdr/searches/$SEARCH_ID/outreach/launch "${H[@]}" -d "$LAUNCH,\"confirm\":true}"const BASE = "https://api.vendo-ai.com/v1";
const KEY = process.env.VENDO_API_KEY;
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
async function vendo(method, path, body) {
const res = await fetch(BASE + path, {
method,
headers: { Authorization: `Bearer ${KEY}`, "Content-Type": "application/json" },
body: body ? JSON.stringify(body) : undefined,
});
const json = await res.json();
if (!res.ok) throw new Error(`${res.status} ${json.code}: ${json.message}`);
return json.data; // 202 (job still running) also lands here
}
// 1. Company + website → company profile + ICP research
const company = { company_name: "Brightsmile Software", website: "brightsmile.io" };
console.log(await vendo("POST", "/sdr/research", { ...company, dry_run: true })); // cost & reuse
const started = await vendo("POST", "/sdr/research", { ...company, confirm: true });
const agentId = started.agent_id;
// 2. Poll: company profile build → research job → done
let st;
do { await sleep(10000); st = await vendo("GET", `/sdr/research?agent_id=${agentId}`); }
while (!st.job && st.setup?.status !== "error");
let job;
do { await sleep(5000); job = (await vendo("GET", `/sdr/research/${st.job.id}`)).job; }
while (!["done", "error"].includes(job.status));
console.log(job.result.profile); // offer, ICP, regions, hunter plan…
// 3. Create & start the search (defaults come from the research)
const { search } = await vendo("POST", "/sdr/searches", {
research_job_id: job.id, target_regions: ["GB"],
});
// 4. Contacts that passed the ICP check
const fits = await vendo("GET", `/sdr/people?search_id=${search.id}&group=fits&limit=50`);
console.log(fits.items.length, "fit contacts so far");
// 5. Messages + launch (preview first)
await vendo("PATCH", `/sdr/searches/${search.id}/outreach/content`, {
email: { mode: "ai", prompt: "Short, specific, one question.", tone: "friendly" },
linkedin: { invite_enabled: true, invite_mode: "ai",
conversation_rules: "Goal: a 15-minute demo. Offer https://cal.com/brightsmile/demo." },
});
const launch = {
channels: ["email", "linkedin"], email_sender_mode: "client_owned", client_email_provider: "google",
timezone: "Europe/London", work_start_hour: 9, work_end_hour: 17, exclude_weekends: true, icp_segment: "fit",
};
const report = await vendo("POST", `/sdr/searches/${search.id}/outreach/launch`, { ...launch, dry_run: true });
console.log(report); // blockers, ready contacts, delivery window
if (report.can_launch) await vendo("POST", `/sdr/searches/${search.id}/outreach/launch`, { ...launch, confirm: true });import os, time, requests
BASE = "https://api.vendo-ai.com/v1"
S = requests.Session()
S.headers["Authorization"] = f"Bearer {os.environ['VENDO_API_KEY']}"
def vendo(method, path, body=None, **params):
r = S.request(method, BASE + path, json=body, params=params or None, timeout=260)
if r.status_code >= 400:
e = r.json()
raise RuntimeError(f"{r.status_code} {e.get('code')}: {e.get('message')}")
return r.json()["data"] # 202 (job still running) also lands here
company = {"company_name": "Brightsmile Software", "website": "brightsmile.io"}
print(vendo("POST", "/sdr/research", {**company, "dry_run": True})) # cost & reuse
agent_id = vendo("POST", "/sdr/research", {**company, "confirm": True})["agent_id"]
st = {}
while not st.get("job"): # company profile build
time.sleep(10)
st = vendo("GET", "/sdr/research", agent_id=agent_id)
if (st.get("setup") or {}).get("status") == "error":
raise RuntimeError(st["setup"])
job = st["job"]
while job["status"] not in ("done", "error"): # ICP research
time.sleep(5)
job = vendo("GET", f"/sdr/research/{job['id']}")["job"]
print(job["result"]["profile"])
search = vendo("POST", "/sdr/searches", {"research_job_id": job["id"], "target_regions": ["GB"]})["search"]
fits = vendo("GET", "/sdr/people", search_id=search["id"], group="fits", limit=50)
print(len(fits["items"]), "fit contacts so far")
vendo("PATCH", f"/sdr/searches/{search['id']}/outreach/content", {
"email": {"mode": "ai", "prompt": "Short, specific, one question.", "tone": "friendly"},
"linkedin": {"invite_enabled": True, "invite_mode": "ai",
"conversation_rules": "Goal: a 15-minute demo. Offer https://cal.com/brightsmile/demo."}})
launch = {"channels": ["email", "linkedin"], "email_sender_mode": "client_owned",
"client_email_provider": "google", "timezone": "Europe/London",
"work_start_hour": 9, "work_end_hour": 17, "exclude_weekends": True, "icp_segment": "fit"}
report = vendo("POST", f"/sdr/searches/{search['id']}/outreach/launch", {**launch, "dry_run": True})
print(report)
if report.get("can_launch"):
vendo("POST", f"/sdr/searches/{search['id']}/outreach/launch", {**launch, "confirm": True})Launch a classic multi-channel campaign
Preview an audience in the People database, save it, create a DRAFT campaign, check readiness with dry_run, then launch. Connect mailboxes with POST /v1/email/mailboxes and LinkedIn accounts with POST /v1/linkedin/accounts/connect (hosted login link) first.
H=(-H "Authorization: Bearer $VENDO_API_KEY" -H "Content-Type: application/json")
API=https://api.vendo-ai.com/v1
FILTERS='{"countries":["United Kingdom"],"positions":["CFO","Finance Director"],"companySizes":["small","medium"],"mustHaveEmail":true}'
# 1. Free preview of the People database, then save the audience
curl -s -X POST $API/audiences/search "${H[@]}" -d "$FILTERS"
AUDIENCE_ID=$(curl -s -X POST $API/audiences "${H[@]}" \
-d "{\"name\": \"UK finance leaders\", \"filters\": $FILTERS}" | jq -r .data.id)
# 2. Senders: your own mailboxes and LinkedIn accounts
curl -s $API/email/mailboxes "${H[@]}"
curl -s $API/linkedin/accounts "${H[@]}"
# 3. Create the campaign (DRAFT — nobody is contacted yet)
CAMPAIGN_ID=$(curl -s -X POST $API/campaigns "${H[@]}" -d "{
\"name\": \"UK finance leaders — Q4\", \"audienceId\": \"$AUDIENCE_ID\", \"agentId\": \"$AGENT_ID\",
\"settings\": {\"channels\": [\"email\", \"linkedin\"], \"dailyEmailLimit\": 80, \"dailyLinkedinLimit\": 40,
\"timezone\": \"Europe/London\", \"workingHours\": [9, 18], \"excludeWeekends\": true,
\"emailSenderMode\": \"client_owned\",
\"emailContent\": {\"mode\": \"ai\", \"prompt\": \"Short, specific, one question.\", \"tone\": \"friendly\"}}}" | jq -r .data.id)
# 4. Preview what the first email will look like
curl -s -X POST $API/campaigns/preview/email "${H[@]}" -d "{\"campaign_id\": \"$CAMPAIGN_ID\"}"
# 5. Launch: readiness report, then confirm
curl -s -X POST $API/campaigns/$CAMPAIGN_ID/launch "${H[@]}" -d '{"dry_run":true}'
curl -s -X POST $API/campaigns/$CAMPAIGN_ID/launch "${H[@]}" -d '{"confirm":true}'
# 6. Watch it
curl -s $API/campaigns/$CAMPAIGN_ID/live-status "${H[@]}"
curl -s "$API/campaigns/$CAMPAIGN_ID/replies" "${H[@]}"const BASE = "https://api.vendo-ai.com/v1";
const KEY = process.env.VENDO_API_KEY;
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
async function vendo(method, path, body) {
const res = await fetch(BASE + path, {
method,
headers: { Authorization: `Bearer ${KEY}`, "Content-Type": "application/json" },
body: body ? JSON.stringify(body) : undefined,
});
const json = await res.json();
if (!res.ok) throw new Error(`${res.status} ${json.code}: ${json.message}`);
return json.data; // 202 (job still running) also lands here
}
const filters = { countries: ["United Kingdom"], positions: ["CFO", "Finance Director"],
companySizes: ["small", "medium"], mustHaveEmail: true };
console.log((await vendo("POST", "/audiences/search", filters)).stats); // free preview
const audience = await vendo("POST", "/audiences", { name: "UK finance leaders", filters });
const campaign = await vendo("POST", "/campaigns", {
name: "UK finance leaders — Q4",
audienceId: audience.id,
agentId: process.env.AGENT_ID,
settings: {
channels: ["email", "linkedin"], dailyEmailLimit: 80, dailyLinkedinLimit: 40,
timezone: "Europe/London", workingHours: [9, 18], excludeWeekends: true,
emailSenderMode: "client_owned",
emailContent: { mode: "ai", prompt: "Short, specific, one question.", tone: "friendly" },
},
});
const ready = await vendo("POST", `/campaigns/${campaign.id}/launch`, { dry_run: true });
console.log(ready.blockers, ready.recipient_count);
if (ready.ready) await vendo("POST", `/campaigns/${campaign.id}/launch`, { confirm: true });
console.log(await vendo("GET", `/campaigns/${campaign.id}/live-status`));import os, time, requests
BASE = "https://api.vendo-ai.com/v1"
S = requests.Session()
S.headers["Authorization"] = f"Bearer {os.environ['VENDO_API_KEY']}"
def vendo(method, path, body=None, **params):
r = S.request(method, BASE + path, json=body, params=params or None, timeout=260)
if r.status_code >= 400:
e = r.json()
raise RuntimeError(f"{r.status_code} {e.get('code')}: {e.get('message')}")
return r.json()["data"] # 202 (job still running) also lands here
filters = {"countries": ["United Kingdom"], "positions": ["CFO", "Finance Director"],
"companySizes": ["small", "medium"], "mustHaveEmail": True}
print(vendo("POST", "/audiences/search", filters)["stats"]) # free preview
audience = vendo("POST", "/audiences", {"name": "UK finance leaders", "filters": filters})
campaign = vendo("POST", "/campaigns", {
"name": "UK finance leaders — Q4", "audienceId": audience["id"],
"agentId": os.environ["AGENT_ID"],
"settings": {"channels": ["email", "linkedin"], "dailyEmailLimit": 80, "dailyLinkedinLimit": 40,
"timezone": "Europe/London", "workingHours": [9, 18], "excludeWeekends": True,
"emailSenderMode": "client_owned",
"emailContent": {"mode": "ai", "prompt": "Short, specific, one question.", "tone": "friendly"}}})
ready = vendo("POST", f"/campaigns/{campaign['id']}/launch", {"dry_run": True})
print(ready["blockers"], ready["recipient_count"])
if ready["ready"]:
vendo("POST", f"/campaigns/{campaign['id']}/launch", {"confirm": True})
print(vendo("GET", f"/campaigns/{campaign['id']}/live-status"))Work the CRM pipeline
Read pipelines and stages, filter leads, add and move leads, comment, reply on the lead's own channel (a real LinkedIn message or email) and schedule a follow-up.
H=(-H "Authorization: Bearer $VENDO_API_KEY" -H "Content-Type: application/json")
API=https://api.vendo-ai.com/v1
# 1. Pipelines and the stages of the General pipeline
curl -s $API/crm/branches "${H[@]}"
curl -s "$API/crm/pipeline-stages?branch=general" "${H[@]}"
# 2. New leads from the website widget in the last 7 days
curl -s "$API/crm/leads?source=widget_chat&range=7d&limit=50" "${H[@]}"
# 3. Add a lead, move it to a stage, comment
LEAD_ID=$(curl -s -X POST $API/crm/leads "${H[@]}" \
-d '{"first_name":"Tom","last_name":"Reyes","company_name":"BrightPay","email":"tom@brightpay.io"}' | jq -r .data.id)
curl -s -X PATCH $API/crm/leads/$LEAD_ID/stage "${H[@]}" -d "{\"stage_id\": \"$STAGE_ID\"}"
curl -s -X POST $API/crm/leads/$LEAD_ID/comments "${H[@]}" -d '{"body":"Asked for pricing for 12 seats."}'
# 4. Full conversation history, then reply on the lead's channel (real message)
curl -s $API/crm/leads/$LEAD_ID/conversation "${H[@]}"
curl -s -X POST $API/crm/leads/$LEAD_ID/reply "${H[@]}" -d '{"text":"Thanks Tom — sending the 12-seat quote today."}'
# 5. Book a follow-up in the CRM calendar
curl -s -X POST $API/crm/calendar/events "${H[@]}" \
-d "{\"title\": \"Call Tom (BrightPay)\", \"start_at\": \"2026-10-28T10:00:00Z\", \"lead_id\": \"$LEAD_ID\"}"const BASE = "https://api.vendo-ai.com/v1";
const KEY = process.env.VENDO_API_KEY;
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
async function vendo(method, path, body) {
const res = await fetch(BASE + path, {
method,
headers: { Authorization: `Bearer ${KEY}`, "Content-Type": "application/json" },
body: body ? JSON.stringify(body) : undefined,
});
const json = await res.json();
if (!res.ok) throw new Error(`${res.status} ${json.code}: ${json.message}`);
return json.data; // 202 (job still running) also lands here
}
const { stages } = await vendo("GET", "/crm/pipeline-stages?branch=general");
const newLeads = await vendo("GET", "/crm/leads?source=widget_chat&range=7d&limit=50");
console.log(newLeads);
const lead = await vendo("POST", "/crm/leads", {
first_name: "Tom", last_name: "Reyes", company_name: "BrightPay", email: "tom@brightpay.io",
});
await vendo("PATCH", `/crm/leads/${lead.id}/stage`, { stage_id: stages[1].id });
await vendo("POST", `/crm/leads/${lead.id}/comments`, { body: "Asked for pricing for 12 seats." });
await vendo("POST", `/crm/calendar/events`, {
title: "Call Tom (BrightPay)", start_at: "2026-10-28T10:00:00Z", lead_id: lead.id,
});import os, time, requests
BASE = "https://api.vendo-ai.com/v1"
S = requests.Session()
S.headers["Authorization"] = f"Bearer {os.environ['VENDO_API_KEY']}"
def vendo(method, path, body=None, **params):
r = S.request(method, BASE + path, json=body, params=params or None, timeout=260)
if r.status_code >= 400:
e = r.json()
raise RuntimeError(f"{r.status_code} {e.get('code')}: {e.get('message')}")
return r.json()["data"] # 202 (job still running) also lands here
stages = vendo("GET", "/crm/pipeline-stages", branch="general")["stages"]
print(vendo("GET", "/crm/leads", source="widget_chat", range="7d", limit=50))
lead = vendo("POST", "/crm/leads", {"first_name": "Tom", "last_name": "Reyes",
"company_name": "BrightPay", "email": "tom@brightpay.io"})
vendo("PATCH", f"/crm/leads/{lead['id']}/stage", {"stage_id": stages[1]["id"]})
vendo("POST", f"/crm/leads/{lead['id']}/comments", {"body": "Asked for pricing for 12 seats."})
vendo("POST", "/crm/calendar/events", {"title": "Call Tom (BrightPay)",
"start_at": "2026-10-28T10:00:00Z", "lead_id": lead["id"]})Connect from Claude via MCP
- Create a key in Settings → API & MCP (or a
vnd_test_…key to explore safely). - Add the server (Claude Code:
claude mcp add --transport http vendo-ai https://api.vendo-ai.com/mcp --header "Authorization: Bearer vnd_live_…"; other clients: MCP setup). - Ask in plain English. Claude picks tools such as
list_agents,sdr_create_searchorpreview_launch_campaignand shows previews before any execute tool.
Example prompts:
- Show my credit balance, my agents and which campaigns are active.
- Create a sales agent "Anna" for acme.com, read the website, write a friendly script and give me the widget embed code.
- Start Vendo SDR for Brightsmile Software (brightsmile.io) targeting the UK. Show me the ICP before creating the search.
- Which SDR contacts in my "UK dental clinics" search are a fit and have an email? Export them to an audience.
- Preview launching outreach for that search with email + LinkedIn, 9:00–17:00 London time, weekdays only. Do not launch yet.
- List CRM leads from the website widget this week, move the ones that booked a meeting to "Qualified" and summarise their conversations.
- Why did campaign "UK SaaS founders, Q4" stop sending? Check live status, mailboxes and LinkedIn accounts.
Zapier & Make
Zapier: add a Webhooks by Zapier › Custom Request step, set the method and URL (for example https://api.vendo-ai.com/v1/crm/leads), add the header Authorization: Bearer vnd_live_… and Content-Type: application/json, and put the JSON body in Data. Make: use the HTTP › Make a request module with the same URL, method and headers and enable Parse response. Because there are no webhooks yet, use a scheduled trigger and a list endpoint (for example new CRM leads with range=24h) to react to new events.
Coverage & limits
The API mirrors the web app: every product screen, agents and their channels, the meetings calendar, Vendo SDR, campaigns, mailboxes, LinkedIn, audiences, CRM, workspace settings, notifications, integrations, referral links and analytics, is available through REST and MCP.
Not available through the API (by design)
- Registration, login and password reset
- Payments, plan changes, credit top-ups, add-on purchases and the Stripe customer portal (billing is read-only)
- Buying phone numbers (use numbers you already own via GET /v1/phone/numbers)
- Referral payout requests (referral links and stats are available)
- The in-app AI assistant chat
- Account deletion, two-factor authentication settings and leaving a workspace
Billing is fully readable (balance, subscription, plan limits, pricing, transactions, invoices, credit usage) but never writable.
Needs a human step
- Connecting a LinkedIn account (
POST /v1/linkedin/accounts/connect) returns a hosted login link that a person completes in a browser. - Google Meet (
GET /v1/agents/:id/google-meet/auth-url) returns a Google OAuth URL to open in a browser. - Email mailboxes are connected with SMTP/IMAP and an app password the mailbox owner creates.
Other limits
- Rate limit 60 requests/minute per key; request bodies up to 50 MB (JSON up to 10 MB).
- Job results (deep scan, script generation) are kept for 1 hour.
- No webhooks or streaming yet, use the polling endpoints.
Changelog
2026-10-27, Round-6: full product parity
Every button of the web app now has an API route and an MCP tool. The release documents 362 REST endpoints (185 new, 89 fixed or changed) and 396 MCP tools (240 new, 79 fixed).
| Area | Endpoints | New | Updated | MCP tools | New / fixed tools |
|---|---|---|---|---|---|
| Agents | 99 | 35 | 32 | 100 | 83 |
| Meetings | 6 | 6 | 0 | 8 | 8 |
| Vendo SDR | 69 | 27 | 20 | 75 | 55 |
| Campaigns | 66 | 52 | 11 | 77 | 67 |
| Audiences | 26 | 16 | 4 | 31 | 26 |
| CRM | 48 | 14 | 15 | 53 | 34 |
| Workspace & account | 30 | 30 | 0 | 34 | 34 |
| Analytics & planner | 4 | 4 | 0 | 4 | 4 |
| Billing (read-only) | 14 | 1 | 7 | 14 | 8 |
New
- Agents: Slide Presenter, Google Meet meetings and transcripts, Telegram chats and voice, Voice Call setup and test calls, WhatsApp accounts, knowledge files, the voices catalogue, conversations and recordings, jobs for long operations.
- Meetings calendar; Vendo SDR wizard and ICP research, per-hunter configuration and stats, ICP QA, company details and exports; campaign emails, replies, AI threads, LinkedIn conversations, mailboxes and senders, phone campaigns, monitor and AI lead discovery, content accounts.
- Custom audiences (upload, enrich, export); CRM pipelines (branches), documents, import, email from the lead card.
- New scopes
workspace:read/workspace:write: team and invitations, profile and notification preferences, in-app notifications, CRM integrations, referral program.
Fixes
- Keys created in Settings → API & MCP are full-access and now receive every scope automatically, including
crm:*, which older Settings keys were missing. - 79 MCP tools that returned errors or ignored arguments were fixed and now match their REST routes.
- Security hardening: every id is now verified against your workspace; foreign ids answer 404.
- Allow-listed request bodies on write routes (clear
forbidden_fielderrors instead of silently ignored fields).
Breaking changes
DELETE /v1/agents/:id/connections/:cid: now follows dry_run / confirm (a call without a flag returns 400 confirmation_required).DELETE /v1/agents/:id/google-meet: now follows dry_run / confirm and removes the GOOGLE_MEET channel like the app.POST /v1/agents: only the documented fields are accepted; anything else returns 400 forbidden_field (metadata may only carry gender).PATCH /v1/agents/:id: allow-listed: status, scripts, voice, slides and contact channels must use their own routes (400 forbidden_field).GET /v1/agents/:id/connections/:cid: no longer returns the embedded agent object or voice-provider ids (voice_agent_ready / inbound_agent_ready flags instead).POST /v1/agents/:id/deep-scan: and POST /v1/agents/:id/generate-script run as jobs and may return 202 with a job_id (see Long jobs).PATCH /v1/campaigns/:id: internal settings keys can no longer be written; agentId and branchId change only while the campaign is a draft.POST /v1/campaigns/:id/pause-channel: requires a valid channel; a missing or unknown channel is now 400 (was 200 with an error body).GET /v1/campaigns: hides archived campaigns unless include_archived=true.POST /v1/sdr/searches: enables all 24 hunters by default when enabled_sources is omitted; target_regions (at least one) is required unless research_job_id provides the regions.PATCH /v1/sdr/searches/:id: explicit allow-list: status, agent_id and internal flags return 400 forbidden_field; hunter ids are validated.GET /v1/sdr/people/:id/detail: now requires search_id.POST /v1/sdr/searches/:id/run-now: without source now runs every enabled runnable hunter (the backend alone defaulted to linkedin_jobs).POST /v1/sdr/searches/:id/pause: pauses hunters and outreach sending together (semantics corrected).POST /v1/sdr/searches/:id/outreach/launch: rejects sender_mode (use email_sender_mode) and validates every window-1 field.PATCH /v1/crm/settings: unknown fields are rejected instead of being merged.
2026-09-07, Round-4/5: re-audit, hunter catalogue and sandbox
- Every route re-audited against the live code.
- New
GET /v1/sdr/hunters: the discoverable signal-hunter catalogue;enabled_sourcesdocumented on search create/update. - Test keys (
vnd_test_) became a true read-only sandbox (403 sandbox_read_onlyon writes).
2026-07-22, Phase 7: UI parity
- Audience Sets marketplace and LinkedIn account management.
- CRM: workspace members, calendar, notifications, settings and the outbound Communications stream.
- Vendo SDR: CSV contact export, per-person provenance, activity feed, outreach summary; more protected routes.
2026-07-06, CRM domain
- REST endpoints and MCP tools for the built-in Vendo CRM; scopes
crm:read/crm:write.
2026-06-15, Platform launch
- Read-only billing; Vendo SDR signal pipeline; full agent lifecycle; the MCP server at
api.vendo-ai.com/mcp; the destructive-action protocol and idempotency keys; the Settings → API & MCP tab; the core API.
What's next
- Outbound webhooks (campaign launched, reply received, contact discovered).
- A downloadable OpenAPI description.
- Per-session credit ceilings for MCP clients.
FAQ
How do I get a Vendo AI API key?
Log in at app.vendo-ai.com, open Settings → API & MCP and create a key. The key (vnd_live_…) is shown once, so copy it immediately. Keys created there are full-access: they receive every scope, including scopes added in later releases.
What is the base URL and how do I authenticate?
REST: https://api.vendo-ai.com/v1. Send the key as "Authorization: Bearer vnd_live_…" or as "X-API-Key: vnd_live_…". MCP: https://api.vendo-ai.com/mcp (Streamable HTTP) with the same Bearer header.
How do I connect Vendo AI to Claude, ChatGPT or Cursor via MCP?
Add the server URL https://api.vendo-ai.com/mcp to your MCP client with the header Authorization: Bearer <your key>. In Claude Code run: claude mcp add --transport http vendo-ai https://api.vendo-ai.com/mcp --header "Authorization: Bearer vnd_live_…". Configs for Claude Desktop, Cursor, VS Code, Windsurf and the OpenAI and Anthropic APIs are on this page.
Can an AI assistant accidentally launch a campaign or delete data?
Destructive, paid or real-world actions use a two-step protocol. Over REST you send {"dry_run": true} to get a preview and then {"confirm": true} to execute; a request with neither returns 400 confirmation_required. Over MCP these actions are split into preview_* and execute tool pairs, so the model has to pick the execute tool explicitly.
Is there a sandbox or test mode?
Yes. Test keys (vnd_test_…) are read-only sandbox keys: GET requests, previews and dry_run calls work, and any real write is refused with 403 sandbox_read_only. Use a live key for real writes.
What happens with long operations such as a website deep scan or script generation?
They run as jobs. The request waits up to wait_seconds (default 200, maximum 240) and returns the result. If the job is still running you get HTTP 202 with a job_id; poll GET /v1/agents/{id}/jobs/{jobId} every 10-20 seconds. ICP research for Vendo SDR is polled with GET /v1/sdr/research/{id}.
Are there webhooks or streaming endpoints?
Not in this release. Everything the web app streams live (SDR activity, research progress, enrichment progress) is available through polling endpoints such as GET /v1/sdr/searches/{id}/outreach/activity-feed, GET /v1/sdr/research/{id} and GET /v1/audiences/custom/{id}/enrichment.
What are the rate limits?
By default 60 requests per minute per API key (sliding window). Responses carry X-RateLimit-Limit and X-RateLimit-Remaining; on 429 rate_limited a Retry-After header tells you how many seconds to wait.
Can the API change my plan, top up credits or buy phone numbers?
No. Billing is read-only by design: there is no billing write scope. Payments, plan changes, top-ups, buying phone numbers and referral payout requests stay in the web app. Registration and login, the in-app AI assistant chat, account deletion, 2FA and leaving a workspace are not available through the API either.
What happens if I use an id that belongs to another workspace?
Every id in a request is verified against the workspace of your API key. An id that belongs to another workspace answers 404 not_found, exactly like an id that does not exist, so the API never reveals whether another tenant's object exists.
Does the API cost extra?
API and MCP access are included in Vendo AI plans. Actions cost the same credits as in the web app (for example a deep website scan is 15 credits, each usable Vendo SDR contact is 5 credits, phone calls are billed per minute); reads are free. See the pricing page for plans.
More: Vendo AI for developers · AI sales agent · General FAQ · Pricing
