API Reference
Full programmatic access to your LeadEngines workspace β contacts, deals, tags, workflows, messaging, calls, and more.
https://app.leadengines.ai/api/v1Authentication
All requests must include an Authorization header with your API key. Create API keys under Settings β API Keys in your workspace.
Authorization: Bearer le_live_your_api_key_hereHow to Connect
Use your API key to connect LeadEngines to AI assistants, external tools, or your own apps.
Connect to AI Assistants (MCP)
Use with Claude Desktop, Cursor, Windsurf, or any MCP-compatible AI tool
LeadEngines supports the Model Context Protocol (MCP), so AI assistants can read your CRM data and take actions like sending SMS, making calls, managing deals, and generating workflows β all through natural language.
Claude Desktop
Open Settings β MCP Servers and add:
{
"mcpServers": {
"leadengines": {
"url": "https://app.leadengines.ai/api/mcp",
"headers": {
"Authorization": "Bearer YOUR_API_KEY"
}
}
}
}Cursor / Windsurf
Open MCP settings and add a new server with the URL https://app.leadengines.ai/api/mcp and your API key as a Bearer token header.
What AI assistants can do:
- Search and manage contacts, deals, and tags
- Send SMS messages and initiate phone calls (including AI voice calls)
- Create and send email campaigns
- Build and activate workflows from natural language
- View pipeline analytics and dashboard stats
See the MCP reference below for the full list of tools and resources.
REST API
Integrate with any language or platform via HTTP
All endpoints are at https://app.leadengines.ai/api/v1/ and require your API key as a Bearer token.
Example: List contacts
curl https://app.leadengines.ai/api/v1/contacts \
-H "Authorization: Bearer YOUR_API_KEY"Example: Send SMS
curl -X POST https://app.leadengines.ai/api/v1/sms \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"contactId": "CONTACT_ID", "body": "Hello!"}'Example: Verify a leadβs phone number
# 1. Text a 6-digit code
curl -X POST https://app.leadengines.ai/api/v1/verify/start \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"phone": "+14155551234"}'
# 2. Confirm it (this is the billable step)
curl -X POST https://app.leadengines.ai/api/v1/verify/check \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"phone": "+14155551234", "code": "123456"}'Pass formId, quizId or pageId to also receive a proof token you can submit to that assetβs public endpoint. Charged per successful verification only.
Available endpoints
TypeScript / JavaScript SDK
Type-safe client for Node.js, React Native, and browser apps
Install the SDK and use typed methods for all API operations.
npm install @leadengines/api-clientUsage
import { LeadEnginesClient } from "@leadengines/api-client";
const client = new LeadEnginesClient({
baseUrl: "https://app.leadengines.ai",
apiKey: "YOUR_API_KEY",
});
// List contacts
const { data, meta } = await client.contacts.list({ search: "alice" });
// Send SMS
await client.sms.send({ contactId: "c1", body: "Hello!" });
// Initiate AI call
await client.calls.initiate({ to: "+15551234567", aiCall: true });
// Assign a tag to a contact
await client.contacts.addTag("contact_id", { tagId: "tag_id" });
// Get dashboard stats
const { data: stats } = await client.dashboard.get();Dashboard
Workspace-level summary stats in a single call.
https://app.leadengines.ai/api/v1/dashboardReturns aggregate counts for contacts, deals, total deal value, active workflows, and conversations.
Response
{
"data": {
"contacts": 842,
"deals": 134,
"totalDealValue": 498500,
"activeWorkflows": 6,
"conversations": 310
}
}Example
curl https://app.leadengines.ai/api/v1/dashboard \
-H "Authorization: Bearer le_live_..."Contacts
Create, read, update, and delete contacts. All operations are scoped to the workspace associated with your API key.
https://app.leadengines.ai/api/v1/contactsList contacts with optional filtering and pagination.
Query parameters
page integer Page number (default: 1)
limit integer Results per page, max 100 (default: 25)
search string Filter by name, email, phone, or company
status string Filter by status (e.g. ACTIVE, LEAD, CUSTOMER)Response
{
"data": [
{
"id": "clx...",
"firstName": "Jane",
"lastName": "Smith",
"email": "jane@example.com",
"phone": "+15550100",
"company": "Acme Corp",
"status": "ACTIVE",
"createdAt": "2026-01-15T10:00:00.000Z"
}
],
"meta": { "total": 842, "page": 1, "limit": 25, "totalPages": 34 }
}Example
curl "https://app.leadengines.ai/api/v1/contacts?page=1&limit=25&status=LEAD" \
-H "Authorization: Bearer le_live_..."https://app.leadengines.ai/api/v1/contactsCreate a single contact. Returns 409 if a contact with the same email already exists.
Request body
{
"firstName": "Jane", // required
"lastName": "Smith", // optional
"email": "jane@example.com", // optional, unique per workspace
"phone": "+15550100", // optional
"company": "Acme Corp", // optional
"title": "CEO", // optional
"status": "LEAD", // optional, default: "ACTIVE"
// Custom fields β auto-created as Text if they don't exist
"customFields": {
"LinkedIn URL": "https://linkedin.com/in/jane",
"Lead Source": "Webinar"
}
}Response
{ "data": { "id": "clx...", "firstName": "Jane", ... } }Example
curl -X POST https://app.leadengines.ai/api/v1/contacts \
-H "Authorization: Bearer le_live_..." \
-H "Content-Type: application/json" \
-d '{
"firstName": "Jane",
"email": "jane@example.com",
"status": "LEAD",
"customFields": { "Lead Source": "Webinar" }
}'https://app.leadengines.ai/api/v1/contacts/:idFetch a single contact by ID, including custom field values and associated deals.
Response
{
"data": {
"id": "clx...",
"firstName": "Jane",
"email": "jane@example.com",
"status": "ACTIVE",
"fieldValues": [
{ "customField": { "name": "Lead Source" }, "value": "Webinar" }
],
"deals": [
{ "deal": { "id": "clx...", "name": "Acme Corp", "value": 5000, "stage": { "name": "Qualified" } } }
]
}
}Example
curl https://app.leadengines.ai/api/v1/contacts/clx_contact_id \
-H "Authorization: Bearer le_live_..."https://app.leadengines.ai/api/v1/contacts/:idUpdate a contact. Only include the fields you want to change.
Request body
{
"firstName": "Jane",
"status": "CUSTOMER",
"company": "Acme Corp"
}Response
{ "data": { "id": "clx...", "firstName": "Jane", "status": "CUSTOMER", ... } }Example
curl -X PATCH https://app.leadengines.ai/api/v1/contacts/clx_contact_id \
-H "Authorization: Bearer le_live_..." \
-H "Content-Type: application/json" \
-d '{ "status": "CUSTOMER" }'https://app.leadengines.ai/api/v1/contacts/:idPermanently delete a contact and all associated data.
Response
{ "data": { "deleted": true } }Example
curl -X DELETE https://app.leadengines.ai/api/v1/contacts/clx_contact_id \
-H "Authorization: Bearer le_live_..."https://app.leadengines.ai/api/v1/contacts/importBulk-create up to 500 contacts in a single request. Invalid rows are reported in errors but don't block the rest.
Request body
{
"records": [
{
"firstName": "Alice",
"email": "alice@example.com",
"status": "ACTIVE",
"customFields": { "Source": "CSV import" }
},
{
"firstName": "Bob",
"email": "bob@example.com"
}
]
}Response
{
"data": {
"created": 2,
"skipped": 0,
"errors": [],
"total": 2
}
}Example
curl -X POST https://app.leadengines.ai/api/v1/contacts/import \
-H "Authorization: Bearer le_live_..." \
-H "Content-Type: application/json" \
-d '{ "records": [...] }'Deals
Create deals and link them to contacts. Fetch your pipeline stages from GET /pipelines to get the required IDs.
https://app.leadengines.ai/api/v1/dealsCreate a single deal in a pipeline stage.
Request body
{
"name": "Acme Corp", // required
"stageId": "clx...", // required β from GET /pipelines
"pipelineId": "clx...", // required β from GET /pipelines
"value": 5000, // optional, numeric
"currency": "USD", // optional, default: "USD"
"closedAt": "2026-06-30", // optional, ISO date
"notes": "Met at conference",
"contactIds": ["clx..."], // optional, link existing contacts
"customFields": {
"Deal Source": "Inbound",
"Priority": "High"
}
}Response
{ "data": { "id": "clx...", "name": "Acme Corp", ... } }Example
curl -X POST https://app.leadengines.ai/api/v1/deals \
-H "Authorization: Bearer le_live_..." \
-H "Content-Type: application/json" \
-d '{
"name": "Acme Corp",
"stageId": "clx_stage_id",
"pipelineId": "clx_pipeline_id",
"value": 5000
}'https://app.leadengines.ai/api/v1/deals/:idFetch a single deal by ID.
Response
{
"data": {
"id": "clx...",
"name": "Acme Corp",
"value": 5000,
"currency": "USD",
"stage": { "name": "Qualified", "color": "#22c55e" }
}
}Example
curl https://app.leadengines.ai/api/v1/deals/clx_deal_id \
-H "Authorization: Bearer le_live_..."https://app.leadengines.ai/api/v1/deals/:idUpdate a deal. Only include the fields you want to change.
Request body
{
"stageId": "clx...",
"value": 7500
}Response
{ "data": { "id": "clx...", "name": "Acme Corp", "value": 7500, ... } }Example
curl -X PATCH https://app.leadengines.ai/api/v1/deals/clx_deal_id \
-H "Authorization: Bearer le_live_..." \
-H "Content-Type: application/json" \
-d '{ "stageId": "clx_stage_id", "value": 7500 }'https://app.leadengines.ai/api/v1/deals/:idPermanently delete a deal.
Response
{ "data": { "deleted": true } }Example
curl -X DELETE https://app.leadengines.ai/api/v1/deals/clx_deal_id \
-H "Authorization: Bearer le_live_..."https://app.leadengines.ai/api/v1/deals/importBulk-create up to 100 deals in a single request.
Request body
{
"records": [
{
"name": "Acme Corp",
"stageId": "clx...",
"pipelineId": "clx...",
"value": 5000,
"customFields": { "Source": "Referral" }
}
]
}Response
{
"data": {
"created": 1,
"skipped": 0,
"errors": [],
"total": 1
}
}Example
curl -X POST https://app.leadengines.ai/api/v1/deals/import \
-H "Authorization: Bearer le_live_..." \
-H "Content-Type: application/json" \
-d '{ "records": [...] }'Pipelines
Fetch your pipelines and stages. The stageId and pipelineId values returned here are required when creating deals.
https://app.leadengines.ai/api/v1/pipelinesReturns all pipelines and their stages for the workspace.
Response
{
"data": [
{
"id": "clx...",
"name": "Sales Pipeline",
"isDefault": true,
"stages": [
{ "id": "clx...", "name": "New Lead", "color": "#6366f1", "order": 0 },
{ "id": "clx...", "name": "Qualified", "color": "#22c55e", "order": 1 },
{ "id": "clx...", "name": "Closed Won", "color": "#14b8a6", "order": 2 }
]
}
]
}Example
curl https://app.leadengines.ai/api/v1/pipelines \
-H "Authorization: Bearer le_live_..."Email Lists
Lists are the audiences campaigns send to. Create a list, add contacts to it, then reference its id in a campaign's listIds.
https://app.leadengines.ai/api/v1/listsList every email list in the workspace with its member count.
Response
{
"data": [
{ "id": "clx...", "name": "Newsletter", "description": null, "_count": { "members": 412 } }
]
}Example
curl https://app.leadengines.ai/api/v1/lists \
-H "Authorization: Bearer le_live_..."https://app.leadengines.ai/api/v1/listsCreate an email list.
Request body
{
"name": "Newsletter",
"description": "Monthly product update"
}Response
{ "data": { "id": "clx...", "name": "Newsletter", "description": "..." } }Example
curl -X POST https://app.leadengines.ai/api/v1/lists \
-H "Authorization: Bearer le_live_..." \
-H "Content-Type: application/json" \
-d '{"name":"Newsletter"}'https://app.leadengines.ai/api/v1/lists/:id/contactsAdd contacts to a list. Idempotent β contacts already on the list are skipped, and ids from another workspace are ignored, so added can be lower than the number supplied.
Request body
{ "contactIds": ["clx...", "clx..."] }Response
{ "data": { "added": 2 } }Example
curl -X POST https://app.leadengines.ai/api/v1/lists/clx.../contacts \
-H "Authorization: Bearer le_live_..." \
-H "Content-Type: application/json" \
-d '{"contactIds":["clx..."]}'Email Campaigns
The full draft β verify β correct β test β send loop. Campaign status is one of DRAFT, SCHEDULED, SENDING, SENT, CANCELLED, PAUSED.
409 NO_VERIFIED_DOMAIN.https://app.leadengines.ai/api/v1/campaignsList campaigns, newest first. Returns the SIZE of each campaign's content rather than the bodies, so a listing stays small β use GET /campaigns/:id for the content itself.
Query parameters
status string DRAFT | SCHEDULED | SENDING | SENT | CANCELLED | PAUSED
search string Match against campaign name or subject
limit number Max results (default 25, max 100)Response
{
"data": [
{
"id": "clx...",
"name": "August newsletter",
"subject": "Your August update",
"status": "DRAFT",
"htmlContentLength": 7231,
"jsonContentLength": 0,
"lists": [{ "id": "clx...", "name": "Newsletter" }],
"totalSent": 0
}
]
}Example
curl "https://app.leadengines.ai/api/v1/campaigns?status=DRAFT" \
-H "Authorization: Bearer le_live_..."https://app.leadengines.ai/api/v1/campaignsCreate a campaign draft. htmlContent alone is enough β the campaign builder parses it when a human opens the draft, so you never need to produce editor JSON. The campaign needs an audience: at least one of listIds, segmentIds, or sendToAllContacts.
Request body
{
"name": "August newsletter", // internal β recipients never see it
"subject": "Your August update",
"htmlContent": "<p>Hello</p>",
"previewText": "Inbox preview line",
"fromName": "Acme", // falls back to the workspace default
"replyTo": "hi@acme.com", // "
"editorType": "visual", // visual | text (default visual)
"listIds": ["clx..."],
"segmentIds": [],
"excludeListIds": [], // suppression β these recipients are skipped
"sendToAllContacts": false,
"jsonContent": null, // only for real Maily/Tiptap editor JSON
"templateId": null
}Response
{ "data": { "id": "clx...", "status": "DRAFT", ... } }Example
curl -X POST https://app.leadengines.ai/api/v1/campaigns \
-H "Authorization: Bearer le_live_..." \
-H "Content-Type: application/json" \
-d '{"name":"August newsletter","subject":"Your August update","htmlContent":"<p>Hello</p>","listIds":["clx..."]}'https://app.leadengines.ai/api/v1/campaigns/:idRead one campaign in full β subject, preview text, the HTML body, its lists/segments/exclusions and its send stats.
Response
{ "data": { "id": "clx...", "htmlContent": "<p>Hello</p>", "lists": [...], "totalOpened": 0, ... } }Example
curl https://app.leadengines.ai/api/v1/campaigns/clx... \
-H "Authorization: Bearer le_live_..."https://app.leadengines.ai/api/v1/campaigns/:idPartially update a DRAFT. Any subset of the create body. Omitted fields keep their current value β including the audience β so fixing one bad link is a one-field PATCH, not a second campaign. Already scheduled? Unschedule it first.
Request body
{ "subject": "Corrected subject line" }Response
{ "data": { campaign } }Example
curl -X PATCH https://app.leadengines.ai/api/v1/campaigns/clx... \
-H "Authorization: Bearer le_live_..." \
-H "Content-Type: application/json" \
-d '{"subject":"Corrected subject line"}'https://app.leadengines.ai/api/v1/campaigns/:idDelete a DRAFT campaign. Use POST /campaigns/:id/cancel for one already scheduled or sending.
Response
{ "data": { "deleted": true } }Example
curl -X DELETE https://app.leadengines.ai/api/v1/campaigns/clx... \
-H "Authorization: Bearer le_live_..."https://app.leadengines.ai/api/v1/campaigns/:id/testSend one [TEST]-prefixed copy to a single address, using the same sender the real send would. Does not change the campaign's status or audience. Rate limited to 10 per 5 minutes per workspace.
Request body
{ "email": "you@example.com" }Response
{ "data": { "sent": true } }Example
curl -X POST https://app.leadengines.ai/api/v1/campaigns/clx.../test \
-H "Authorization: Bearer le_live_..." \
-H "Content-Type: application/json" \
-d '{"email":"you@example.com"}'https://app.leadengines.ai/api/v1/campaigns/:id/scheduleSchedule a DRAFT (or reschedule an already-scheduled campaign). scheduledAt must be ISO 8601 with a timezone.
Request body
{ "scheduledAt": "2026-08-15T15:00:00Z" }Response
{ "data": { "scheduledAt": "2026-08-15T15:00:00Z" } }Example
curl -X POST https://app.leadengines.ai/api/v1/campaigns/clx.../schedule \
-H "Authorization: Bearer le_live_..." \
-H "Content-Type: application/json" \
-d '{"scheduledAt":"2026-08-15T15:00:00Z"}'https://app.leadengines.ai/api/v1/campaigns/:id/scheduleUnschedule: SCHEDULED back to DRAFT, clearing the send time so the campaign can be edited and rescheduled.
Response
{ "data": { "status": "DRAFT" } }Example
curl -X DELETE https://app.leadengines.ai/api/v1/campaigns/clx.../schedule \
-H "Authorization: Bearer le_live_..."https://app.leadengines.ai/api/v1/campaigns/:id/sendSend a DRAFT to its whole audience immediately. Irreversible once delivery starts β send a test first.
Response
{ "data": { "status": "SENDING", "recipientCount": 128 } }Example
curl -X POST https://app.leadengines.ai/api/v1/campaigns/clx.../send \
-H "Authorization: Bearer le_live_..."https://app.leadengines.ai/api/v1/campaigns/:id/cancelCancel a SCHEDULED or SENDING campaign. Already-delivered emails cannot be recalled; this stops the rest.
Response
{ "data": { "status": "CANCELLED" } }Example
curl -X POST https://app.leadengines.ai/api/v1/campaigns/clx.../cancel \
-H "Authorization: Bearer le_live_..."Campaign error codes
400 VALIDATION_ERROR message names the offending field
404 NOT_FOUND no such campaign here, or not a DRAFT for a DRAFT-only call
409 CAMPAIGN_INCOMPLETE needs both a subject and HTML content
409 NO_RECIPIENTS the audience resolved to zero contacts
409 NO_VERIFIED_DOMAIN add a sending domain in Email Settings
409 SENDING_SUSPENDED sending is paused for this workspace
429 RATE_LIMITED test-send burst limit; see Retry-AfterWorkflows
Manage and enroll contacts in automated workflows (Engines). Workflows with API trigger type can be enrolled into via POST /engines/:id/enroll.
https://app.leadengines.ai/api/v1/workflowsList all workflows in the workspace.
Query parameters
status string Filter by status: DRAFT, ACTIVE, PAUSED, ARCHIVEDResponse
{
"data": [
{
"id": "clx...",
"name": "New Lead Welcome",
"status": "ACTIVE",
"triggers": [{ "type": "CONTACT_CREATED" }],
"_count": { "steps": 5, "enrollments": 128 }
}
]
}Example
curl "https://app.leadengines.ai/api/v1/workflows?status=ACTIVE" \
-H "Authorization: Bearer le_live_..."https://app.leadengines.ai/api/v1/workflows/:idFetch a single workflow with full triggers and steps.
Response
{
"data": {
"id": "clx...",
"name": "New Lead Welcome",
"status": "ACTIVE",
"triggers": [{ "id": "clx...", "type": "API", "config": null }],
"steps": [
{ "order": 0, "type": "SEND_SMS", "config": "{...}", "name": "Welcome text" },
{ "order": 1, "type": "WAIT", "config": "{"delayMinutes":1440}" }
],
"_count": { "enrollments": 42 }
}
}Example
curl https://app.leadengines.ai/api/v1/workflows/clx_workflow_id \
-H "Authorization: Bearer le_live_..."https://app.leadengines.ai/api/v1/workflows/:idUpdate the status of a workflow.
Request body
{
"status": "ACTIVE" // DRAFT | ACTIVE | PAUSED | ARCHIVED
}Response
{ "data": { "updated": true } }Example
curl -X PATCH https://app.leadengines.ai/api/v1/workflows/clx_workflow_id \
-H "Authorization: Bearer le_live_..." \
-H "Content-Type: application/json" \
-d '{ "status": "PAUSED" }'https://app.leadengines.ai/api/v1/workflows/:idDelete a workflow and all its enrollments.
Response
{ "data": { "deleted": true } }Example
curl -X DELETE https://app.leadengines.ai/api/v1/workflows/clx_workflow_id \
-H "Authorization: Bearer le_live_..."https://app.leadengines.ai/api/v1/engines/:id/enrollEnroll one or more contacts in an active workflow. Contacts already enrolled are silently skipped. The workflow must have at least one step.
Request body
{
"contactIds": ["clx...", "clx..."], // required
"dealId": "clx..." // optional β associate a deal with the enrollment
}Response
{ "data": { "enrolledCount": 2 } }Example
curl -X POST https://app.leadengines.ai/api/v1/engines/clx_workflow_id/enroll \
-H "Authorization: Bearer le_live_..." \
-H "Content-Type: application/json" \
-d '{ "contactIds": ["clx_contact_id"] }'Webhooks (Inbound)
Send contacts from Zapier, Whop, Make, or any third-party tool directly into an Engine β no API key required. LeadEngines creates or finds the contact from the payload and enrolls them automatically.
https://app.leadengines.ai/api/webhooks/engines/:secretPublic endpoint β no Authorization header needed. The secret in the URL authenticates the request. The engine must be ACTIVE and have the Webhook / API trigger enabled.
Request body
{
"email": "john@example.com", // required if no phone
"phone": "+15551234567", // required if no email
"firstName": "John", // optional
"lastName": "Doe", // optional
"company": "Acme Inc" // optional
}Smart field detection
LeadEngines automatically tries common key variants for each field, so you don't need to rename your payload:
| Contact field | Keys tried (in order) |
|---|---|
| email, email_address, emailAddress, contact_email | |
| phone | phone, phone_number, phoneNumber, mobile, cell |
| firstName | firstName, first_name, firstname, fname, given_name |
| lastName | lastName, last_name, lastname, lname, surname |
| company | company, company_name, companyName, organization, org |
You can also configure custom field mapping per-engine in the canvas trigger config (e.g. map customer_email β email).
Response
{ "ok": true }Example β curl
curl -X POST https://app.leadengines.ai/api/webhooks/engines/YOUR_SECRET \
-H "Content-Type: application/json" \
-d '{
"email": "john@example.com",
"first_name": "John",
"last_name": "Doe",
"phone": "+15551234567",
"company": "Acme Inc"
}'Zapier setup
- In your Engine canvas, add the Webhook / API trigger and click Generate Webhook URL
- In Zapier, add a Webhooks by Zapier β POST action
- Paste the webhook URL and set Payload Type to
json - Map your Zap fields to
email,phone,firstName, etc.
| 400 | Payload must contain at least one of: email or phone |
| 400 | Invalid JSON body |
| 404 | Wrong secret or engine is not ACTIVE |
| 500 | Internal error |
Conversations
Access SMS conversations with contacts.
https://app.leadengines.ai/api/v1/conversationsList all conversations, ordered by most recent message.
Query parameters
page integer Page number (default: 1)
limit integer Results per page, max 100 (default: 25)Response
{
"data": [
{
"id": "clx...",
"lastMessageAt": "2026-04-09T11:30:00.000Z",
"contact": {
"id": "clx...",
"firstName": "Jane",
"lastName": "Smith",
"email": "jane@example.com"
}
}
],
"meta": { "total": 310, "page": 1, "limit": 25, "totalPages": 13 }
}Example
curl "https://app.leadengines.ai/api/v1/conversations?limit=10" \
-H "Authorization: Bearer le_live_..."SMS
Send outbound SMS messages to contacts. Your workspace must have a provisioned phone number configured.
https://app.leadengines.ai/api/v1/smsSend an SMS message to a contact by their ID.
Request body
{
"contactId": "clx...", // required
"body": "Hi {{firstName}}, just checking in!" // required, max 1600 chars
}Response
{ "data": { "id": "clx...", "body": "Hi Jane, just checking in!", ... } }Example
curl -X POST https://app.leadengines.ai/api/v1/sms \
-H "Authorization: Bearer le_live_..." \
-H "Content-Type: application/json" \
-d '{ "contactId": "clx_contact_id", "body": "Hey, are you still interested?" }'| 422 | CONTACT_NO_PHONE | Contact has no phone number |
| 422 | OPT_OUT | Contact has opted out of SMS |
| 422 | UNVERIFIED | Contact phone is not verified |
| 422 | PHONE_NOT_CONFIGURED | No phone number provisioned for workspace |
| 429 | RATE_LIMIT | Too many messages β slow down |
| 403 | SUSPENDED | Workspace is suspended |
Calls
Initiate outbound calls β standard or AI-powered voice agent calls. Requires a provisioned phone number.
https://app.leadengines.ai/api/v1/callsInitiate an outbound phone call to any number. Set aiCall to true to use the AI voice agent.
Request body
{
"to": "+15550100", // required, E.164 format
"contactId": "clx...", // optional β link call to a contact record
"aiCall": false, // optional, default: false
// Only used when aiCall is true:
"aiConfig": {
"systemPrompt": "You are a friendly sales rep calling to schedule a demo...",
"assistantId": "clx..." // optional β use a saved AI assistant config
}
}Response
{ "data": { "callId": "telnyx-call-id", ... } }Example
curl -X POST https://app.leadengines.ai/api/v1/calls \
-H "Authorization: Bearer le_live_..." \
-H "Content-Type: application/json" \
-d '{
"to": "+15550100",
"contactId": "clx_contact_id",
"aiCall": true,
"aiConfig": { "systemPrompt": "You are scheduling a product demo..." }
}'Search
Cross-entity search across contacts and deals in a single request.
https://app.leadengines.ai/api/v1/searchSearch contacts and deals by keyword. Results are grouped by entity type.
Query parameters
q string Search query (searches name, email, phone, company)Response
{
"data": {
"contacts": [
{ "id": "clx...", "firstName": "Jane", "email": "jane@example.com", ... }
],
"deals": [
{ "id": "clx...", "name": "Acme Corp", "value": 5000, ... }
]
}
}Example
curl "https://app.leadengines.ai/api/v1/search?q=acme" \
-H "Authorization: Bearer le_live_..."Custom Fields
You don't need to pre-configure custom fields before using them. Simply pass a customFields object with key-value pairs and LeadEngines will automatically create the fields (as Text type) the first time it sees them.
How it works
- Pass
"customFields": { "Lead Source": "Webinar" }in your request - LeadEngines checks if a field named Lead Source exists in your workspace
- If not, it creates a new Text field called Lead Source
- The value Webinar is saved against that field for the contact or deal
- The field now appears in your workspace under Settings β Custom Fields
MCP Server (AI Integration)
LeadEngines ships a built-in Model Context Protocol (MCP) server. Connect it to Claude, Cursor, or any MCP-compatible AI client to let AI agents read and write your CRM data directly.
Connection
MCP endpoint: https://app.leadengines.ai/api/mcp
Auth header: Authorization: Bearer le_live_your_api_key_hereThe same API key used for the REST API authenticates MCP. The server uses the Streamable HTTP transport.
Available Resources (read-only context)
| URI | Description |
|---|---|
| leadengines://contacts | Last 100 contacts with tags |
| leadengines://deals | Last 100 deals with stage info |
| leadengines://pipelines | All pipelines with stages and deal counts |
| leadengines://conversations | Last 50 SMS conversations |
| leadengines://workflows | All workflows with status and trigger info |
| leadengines://tags | All tags with contact counts |
| leadengines://dashboard | Aggregate workspace stats |
Available Tools (read + write actions)
The email and list tools also take an optional subAccountId β omit it with a workspace-scoped key, supply it with an admin-scoped key to choose which workspace to act on.
| Tool | Parameters | Description |
|---|---|---|
| create_contact | firstName, lastName?, email?, phone?, company?, title?, status?, tagIds?, smsOptIn? | Create a new contact |
| delete_contact | contactId | Delete a contact by ID |
| bulk_update_contact_status | contactIds[], status | Update status for multiple contacts |
| search | query | Search contacts and deals by keyword |
| list_tags | β | List all workspace tags |
| create_tag | name, color? | Create a tag |
| add_tags_to_contact | contactId, tagIds[] | Assign tags to a contact |
| remove_tags_from_contact | contactId, tagIds[] | Remove tags from a contact |
| list_custom_fields | entity? (CONTACT | DEAL) | List custom fields |
| create_custom_field | name, type, entity, required?, options? | Create a custom field on CONTACT or DEAL |
| create_deal | name, stageId, value?, contactIds?, notes?, currency? | Create a new deal |
| move_deal | dealId, stageId | Move a deal to a different stage |
| delete_deal | dealId | Delete a deal |
| create_pipeline | name, isDefault? | Create a pipeline with default stages |
| create_pipeline_stage | pipelineId, name, color?, order | Add a stage to a pipeline |
| list_email_lists | β | List email lists (audiences) |
| create_list | name, description? | Create an email list |
| add_contacts_to_list | listId, contactIds[] | Add contacts to a list |
| create_email_template | name, subject?, htmlContent?, jsonContent?, editorType? | Create an email template |
| list_email_campaigns | status?, search?, limit? | List campaigns (content sizes, not bodies) |
| get_email_campaign | campaignId | Read one campaign in full |
| create_email_campaign | name, subject, htmlContent?, previewText?, fromName?, replyTo?, templateId?, editorType?, jsonContent?, listIds[]?, segmentIds[]?, excludeListIds[]?, sendToAllContacts? | Create a campaign draft |
| update_email_campaign | campaignId + any create field (all optional) | Correct a DRAFT in place |
| send_test_email | campaignId, email | Send one [TEST] copy (rate limited) |
| delete_email_campaign | campaignId | Delete a DRAFT campaign |
| send_campaign_now | campaignId | Send a campaign immediately |
| schedule_campaign | campaignId, scheduledAt (ISO 8601) | Schedule a campaign for later |
| unschedule_campaign | campaignId | SCHEDULED β DRAFT so it can be edited |
| cancel_campaign | campaignId | Cancel a scheduled or sending campaign |
| create_form | name, slug?, fields[], createContact?, tagOnSubmission?, completionWorkflowId?, publish? | Create a lead-capture form |
| create_quiz | name, questions?[], resultHeadline?, resultBody?, publish? | Create a lead-capture quiz |
| send_sms | contactId, message | Send an SMS to a contact |
| generate_ai_sms_reply | conversationId | Generate an AI reply suggestion |
| initiate_call | to, contactId?, aiCall?, systemPrompt? | Initiate an outbound call |
| initiate_ai_call_to_contact | contactId, systemPrompt? | AI voice call to a contact |
| activate_workflow | workflowId | Activate a workflow |
| pause_workflow | workflowId | Pause a workflow |
| enroll_contacts_in_workflow | workflowId, contactIds[] | Enroll contacts in a workflow |
| generate_workflow | prompt | Generate workflow steps from natural language using AI |
MCP Builder β AI Design Sites via Claude Subscription
The MCP Builder lets you build complete AI Design websites, standalone landing pages, and multi-step funnels inside any sub-account you have access to, powered by your own Claude.ai subscription (Pro, Max, or higher) instead of platform AI credits or sub-account wallet billing. Every LLM token bills against your subscription's 5-hour message window β the LeadEngines side never makes an Anthropic API call.
This feature is gated to Enterprise plans only. Plans without allowMcpBuilder = true reject the website-builder tools at call time with "The MCP builder is not enabled on this workspace's plan". Super-admins bypass the check, but the underlying CRM MCP tools (contacts, deals, etc.) remain available on every plan.
Two API key scopes
| Scope | Who issues | Targets | Use |
|---|---|---|---|
| SUB_ACCOUNT | Sub-account admin (Settings β API Keys) | One workspace | CRM MCP tools + (on enterprise plans) builder tools scoped to that workspace |
| USER_ADMIN | Super-admin only (/admin/mcp-keys) | Any sub-account the user can access | Cross-workspace builder via a per-call subAccountId argument |
Both key types use the same le_live_β¦ format and the same https://app.leadengines.ai/api/mcp endpoint β Claude doesn't need to know which scope it's holding.
Connecting from Claude Desktop
// ~/Library/Application Support/Claude/claude_desktop_config.json
{
"mcpServers": {
"leadengines": {
"url": "https://app.leadengines.ai/api/mcp",
"headers": {
"Authorization": "Bearer le_live_your_admin_key_here"
}
}
}
}For Claude Code: claude mcp add leadengines https://app.leadengines.ai/api/mcp --header "Authorization: Bearer le_live_...".
Builder tools
None of these tools call Anthropic. They accept HTML produced by Claude β the LLM work happens inside Claude's own chat using your subscription quota.
| Tool | Parameters | Description |
|---|---|---|
| list_my_sub_accounts | β | Discover sub-accounts you can target. Returns mcpBuilderEnabled per row. |
| list_websites | subAccountId? | List AI Design websites in a sub-account. |
| get_website | subAccountId?, websiteId | Fetch an existing site's metadata + pages + a chrome-only reference HTML. Call this BEFORE adding pages to an existing site so new pages match the visual language. |
| create_website | subAccountId?, name, slug? | Create a NEW AI Design website. Returns websiteId + final slug. |
| add_page | subAccountId?, websiteId, websitePath (null for home), name, html | Add a page to an existing website. Pass the FULL HTML5 document. |
| publish_website | subAccountId?, websiteId | Flip status from DRAFT to PUBLISHED. |
| delete_website | subAccountId?, websiteId | Delete a website. Its pages are detached (kept), not deleted. |
| list_pages | subAccountId?, standaloneOnly? | List pages (standalone by default; pass false for website/funnel pages too). |
| create_page | subAccountId?, name, slug?, html, publish? | Create a standalone AI Design landing page (served at /p/<sub>/<slug>). |
| publish_page | subAccountId?, pageId | Publish a standalone page. |
| list_funnels | subAccountId? | List funnels in a sub-account. |
| create_funnel | subAccountId?, name, slug?, description? | Create an AI Design funnel (ordered step pages). |
| add_funnel_step | subAccountId?, funnelId, name, stepSlug?, html | Append a step page to a funnel. Link steps with data-le-link="next". |
| publish_funnel | subAccountId?, funnelId | Publish a funnel + all its step pages (served at /f/<sub>/<slug>). |
| get_page_html | subAccountId?, pageId | Fetch one page's full HTML β works for website, standalone, and funnel pages. |
| set_page_html | subAccountId?, pageId, html | Replace any page's HTML in-place (website/standalone/funnel). |
| list_media_assets | subAccountId?, type? (all | image | document), limit? | List the workspace media library β images and documents (PDF, Office, CSV). Reuse these URLs instead of inventing stock ones. |
| upload_media_asset | subAccountId?, sourceUrl OR contentBase64, name? | Upload/rehost a file into the media library and get a permanent public URL. Images, PDF, Word/Excel/PowerPoint, .txt, .csv. Max 20 MB. |
| register_media_asset | subAccountId?, url, name?, mimeType?, sizeBytes? | Bookmark an already-hosted URL in the library without rehosting it. |
MCP prompts
| Prompt | Args | Returns |
|---|---|---|
| v2_design_system | brand? | The full LeadEngines v2 design-system seed: Tailwind Play CDN + framer-motion + page-shell SEO contract + component library + anti-slop directives + per-page token budget. Invoke this before generating any HTML. |
Sample prompt to give Claude
Using LeadEngines MCP, list my sub-accounts. Then load the
v2_design_system prompt and build a 6-page website for Acme
Bookkeeping β a small-business bookkeeper with a warm but
professional voice. Put it in the "Praxis Marketing" sub-account.
Pages: home, services, three service detail pages, contact.
Publish when done.Claude will pick the right sub-account, call create_website, generate each page's HTML following the v2 design system, call add_page per page, and call publish_website at the end. The site appears in the chosen sub-account's Websites list immediately.
Adding pages to a site that already exists
Using LeadEngines MCP, find the "Busy Business Solutions" website
in the same sub-account. Use get_website on it to see the existing
chrome and the page paths that are already taken. Then add three
new service pages β tax prep, payroll, and CFO services β that
match the existing design verbatim.Claude calls list_websites β get_website β reads the returned referenceHtml (the home's head + header + footer, stripped of body content) β writes new pages that reuse the exact same Tailwind config, fonts, header nav, and footer β calls add_page per new page. The existing pages are untouched.
Same idea for iterating on a single page: get_page_html β rewrite βset_page_html with the same pageId.
Lead-capture forms β automatically wired to LeadEngines
Any form on an MCP-built page is automatically captured into the same sub-account's Contacts β no extra configuration needed. The pipeline:
- The
v2_design_systemprompt tells Claude to emit<form data-le-form="lead">with named inputs (name,email,phone,company,message). - If Claude forgets the attribute, a render-time safety net auto-tags any plain
<form>at the time the page is served (forms with an externalaction=...URL are left alone β they're clearly pointed elsewhere). - The AI Design runtime intercepts the submit, POSTs to
/api/ai-design-form/[pageId], and creates a Contact in the website's sub-account. The Activity log shows which page/form the lead came from viale_source.
Where the lead lands: in the sub-account that owns the website. If you want BBS leads to land in the BBS workspace, build the BBS site inside that sub-account (create_website with that sub-account's ID).
Test it: after publish_website the public URL captures live submissions. For preview testing, use the auth-gated editor preview β the runtime fires there too.
Best practice when prompting: don't over-specify the form. The design-system seed already covers everything. *"Add a 5-page site for Acme with a lead-capture form on the contact and home pages"* is enough β Claude handles the rest.
Subscription rate-limit reality. Each add_page turn is one chat message against your Claude.ai 5-hour window. Max Plus fits a full ~6-page site comfortably; Pro will start hitting limits on bigger sites. If Claude tells you it's out of usage, anything already saved is permanent βadd_page commits page-by-page, not all-or-nothing.
What this is not. The MCP Builder is purely additive β it doesn't replace the in-app "AI Design β Website" button. End users on Pro / Starter plans still use that button, which generates via the platform's Anthropic API key (or their BYO key) and bills against wallet credits as always. The MCP Builder is a separate admin/enterprise surface, layered on top, gated by allowMcpBuilder.
Errors
All errors return a JSON body with an error object.
| Status | Code | Meaning |
|---|---|---|
| 401 | UNAUTHORIZED | Missing, invalid, or expired API key |
| 400 | VALIDATION_ERROR | Required field missing or invalid value |
| 400 | MISSING_TAG_ID | tagId query parameter required (DELETE contact tag) |
| 404 | NOT_FOUND | Resource does not exist in this workspace |
| 409 | DUPLICATE | A record with this value already exists (e.g. duplicate email or tag name) |
| 422 | NO_STEPS | Workflow has no steps β cannot enroll or activate |
| 422 | CONTACT_NO_PHONE | Contact has no phone number on file |
| 422 | OPT_OUT | Contact has opted out of SMS |
| 422 | PHONE_NOT_CONFIGURED | Workspace has no provisioned phone number |
| 429 | RATE_LIMIT | Too many requests β slow down |
| 403 | SUSPENDED | Workspace account is suspended |
| 500 | INTERNAL_ERROR | Unexpected server error β try again |
// Error response shape
{ "error": { "code": "UNAUTHORIZED", "message": "Optional detail" } }