crm.leads.list & crm.leads.search
#
Query leads across your module. Use crm.leads.list for structured multi-field filtering by stage, assignment, or status, and crm.leads.search for keyword matching across lead names, emails, and custom metadata attributes.
TypeScript Signatures #
typescript
// Structured multi-field filtering with pagination
crm.leads.list(filters?: {
page?: number;
limit?: number;
status?: Array<"new" | "contacted" | "qualified" | "unqualified" | "converted">;
query?: string;
assignedTo?: number;
stageId?: number;
}): Promise<PaginatedResult<Lead>>;
// Full-text keyword search across lead fields
crm.leads.search(
query: string,
options?: { page?: number; limit?: number }
): Promise<PaginatedResult<Lead>>;
Parameters Reference #
list(filters)
#
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
filters.page | number | Optional | 1 | Page number index (1-based). |
filters.limit | number | Optional | 50 | Maximum results per page (capped at 100). |
filters.status | string[] | Optional | — | Array of lead status strings to match against. |
filters.assignedTo | number | Optional | — | Filter by the assigned user ID. |
filters.stageId | number | Optional | — | Filter by current pipeline stage ID. |
filters.query | string | Optional | — | Substring match against name, email, or phone. |
search(query, options)
#
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
query | string | Yes | — | Search string to match across all searchable fields. |
options.page | number | Optional | 1 | Page number index. |
options.limit | number | Optional | 20 | Results per page (capped at 100). |
Returns: Promise<PaginatedResult<Lead>>
typescript
interface PaginatedResult<T> {
data: T[];
total: number;
}
Minimal Quickstart Example #
typescript
async function run(context: Context, dolfin: DolfinApi): Promise<void> {
const { lead } = context;
const crm = await dolfin.crm.module(lead.moduleId);
// Search for other leads from the same company domain
const domain = lead.email?.split("@")[1];
if (domain) {
const results = await crm.leads.search(domain, { limit: 10 });
console.log(`Found ${results.total} matching leads with domain @${domain}`);
}
}
Production Recipe: Inbound Duplicate Detection & Alerting #
This recipe runs on newly created leads, executes a full-text search against the incoming email address, detects existing records, and tags the lead while appending warning notes for the sales team.
typescript
async function run(context: Context, dolfin: DolfinApi): Promise<void> {
const { lead } = context;
const crm = await dolfin.crm.module(lead.moduleId);
if (!lead.email) {
console.log("No email provided; skipping duplicate detection.");
return;
}
console.log(`Checking duplicates for lead #${lead.id} (${lead.email})`);
try {
// 1. Search existing leads with the same email
const searchResults = await crm.leads.search(lead.email, { limit: 10 });
// 2. Filter out current lead record
const existingDuplicates = searchResults.data.filter((match) => match.id !== lead.id);
if (existingDuplicates.length > 0) {
console.warn(`Detected ${existingDuplicates.length} potential duplicate records.`);
const duplicateSummary = existingDuplicates
.map((d) => `#${d.id} ("${d.name}" in Stage ID ${d.stageId})`)
.join(", ");
// 3. Mark duplicate flag in custom metadata
await crm.leads.update(lead.id, {
meta: {
...lead.meta,
has_possible_duplicate: true,
duplicate_lead_ids: existingDuplicates.map((d) => d.id),
detected_at: new Date().toISOString()
}
});
// 4. Record warning timeline note
await crm.leads.notes.add(
lead.id,
`⚠️ Duplicate Alert: Found ${existingDuplicates.length} existing lead(s) with matching email: ${duplicateSummary}.`
);
console.log(`Successfully tagged duplicate warnings on lead #${lead.id}.`);
} else {
console.log("No duplicates found. Lead is unique.");
}
} catch (error: any) {
console.error(`Duplicate detection query failed: ${error.message}`);
throw error;
}
}
Common Errors & Troubleshooting #
| Error Message | Cause | Resolution |
|---|---|---|
Search query is required | Passed an empty string "" or non-string query to search(). | Provide a non-empty keyword string. |
Exceeded maximum CRM calls per execution (50) | Iterated through pages or records issuing queries in an unmetered loop. | Request larger page sizes (up to limit: 100) rather than querying 1 item per call. |