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:

typescript
const crm = await dolfin.crm.module(lead.moduleId);

Why Module Scoping is Required #

  1. Tenant Isolation: Ensures custom functions can only operate on records within the authorized tenant.
  2. Dynamic Metadata Validation: Automatically hydrates the TypeScript autocomplete definitions for all custom fields defined in that specific module's Builder.
  3. Pipeline & Stage Consistency: Validates stage progression against the distinct pipeline configurations belonging to that module.
📌 Important

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:

typescript
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]:

typescript
const dealValue = Number(lead.meta?.deal_value || 0);
const industry = String(lead.meta?.industry || "Unknown");

Complete API Method Reference #

MethodReturn TypePurpose
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.

typescript
// 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");
💡 Tip

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.