dolfinApi.crm — Module-Scoped CRM Operations
#
The dolfin.crm namespace provides full programmatic control over leads, dynamic custom metadata attributes, pipeline stages, assignments, internal notes, and activity audit trails.
Module Scoping Pattern #
Because Dolfin is a multi-module CRM architecture (supporting custom modules like Sales Deals, Real Estate Listings, Enterprise Support), all CRM operations are scoped to a specific module ID:
const crm = await dolfin.crm.module(lead.moduleId);
Why Module Scoping is Required #
- Tenant Isolation: Ensures custom functions can only operate on records within the authorized tenant.
- Dynamic Metadata Validation: Automatically hydrates the TypeScript autocomplete definitions for all custom fields defined in that specific module's Builder.
- Pipeline & Stage Consistency: Validates stage progression against the distinct pipeline configurations belonging to that module.
Always pass lead.moduleId from the ambient context object when calling dolfin.crm.module(lead.moduleId). Do not hardcode numeric module IDs across different environments.
The Context Data Structure
#
When a custom function executes, the runtime injects an immutable context snapshot representing the entity that triggered the execution:
interface Context {
lead: {
id: number;
name: string;
email: string | null;
phone: string | null;
countryCode: string | null;
status: "new" | "contacted" | "qualified" | "unqualified" | "converted";
moduleId: number;
pipelineId: number;
stageId: number;
assignedTo: number | null;
teamId: number | null;
createdAt: string;
updatedAt: string;
stageUpdatedAt: string | null;
/** Dynamic custom fields defined in the Module Builder */
meta: Record<string, unknown>;
};
}
Accessing Custom Fields (lead.meta)
#
Any custom fields created in the Dolfin Module Builder (e.g. deal_value, contract_tier, industry) are accessible via lead.meta[fieldName]:
const dealValue = Number(lead.meta?.deal_value || 0);
const industry = String(lead.meta?.industry || "Unknown");
Complete API Method Reference #
| Method | Return Type | Purpose |
|---|---|---|
crm.leads.getById(id) | Promise<Lead> | Fetches the latest database state of a lead. |
crm.leads.update(id, data) | Promise<Lead> | Mutates writable lead fields (name, email, phone, meta). |
crm.leads.changeStage(id, pipeline, stage) | Promise<Lead> | Transitions lead to a stage and executes stage automations. |
crm.leads.assign(id, { userId, teamId }) | Promise<Lead> | Reassigns lead owner user or team. |
crm.leads.create(data) | Promise<Lead> | Programmatically creates a new lead in this module. |
crm.leads.list(filters) | Promise<PaginatedResult<Lead>> | Lists leads matching structured filter criteria. |
crm.leads.search(query, options) | Promise<PaginatedResult<Lead>> | Full-text searches leads across names, emails, and custom metadata. |
crm.leads.notes.add(id, content) | Promise<Note> | Appends an internal CRM note to the lead timeline. |
crm.leads.activities.list(id, options) | Promise<PaginatedResult<Activity>> | Retrieves historical audit logs and stage movements. |
crm.stages.list() | Promise<Stage[]> | Returns the ordered list of all stages for this module. |
crm.pipelines.list() | Promise<Pipeline[]> | Returns all pipelines configured for this module. |
Call Budget Management #
Each custom function invocation receives a dedicated budget of 50 CRM operations.
// Consumes 1 call from the 50-call budget
const lead = await crm.leads.getById(1024);
// Consumes 1 call from the 50-call budget
await crm.leads.notes.add(lead.id, "Follow-up required");
If you need to perform composite operations across multiple records, use crm.leads.list({ limit: 50 }) to fetch leads in a single bulk operation rather than calling getById() sequentially inside a loop.