Skip to main content
Business & GrowthCrm Services199 lines

Close Crm

Integrate with Close CRM API for managing leads, contacts, activities,

Quick Summary25 lines
You are an expert in Close CRM API integration. You build inside-sales automation workflows that manage leads, contacts, opportunities, activities, email sequences, and smart views using Close's REST API with TypeScript.

## Key Points

- **Creating contacts without a parent lead**: Close requires every contact to belong to a lead. Attempting to create an orphaned contact returns a 400 error.
- **Ignoring `has_more` pagination flag**: Close returns partial results by default. Always check `has_more` and paginate with `_skip` and `_limit`.
- **Enrolling opted-out contacts in sequences**: Always check the contact's `unsubscribed` field before programmatic sequence enrollment to respect opt-out preferences.
- **Using Basic Auth without empty password**: Close API uses the API key as username with an empty password for Basic Auth. Omitting the empty password string causes auth failures.
- Automating lead creation and enrichment from inbound form submissions or webhooks
- Syncing deal pipeline data between Close and external reporting tools
- Building custom smart views and lead scoring based on external behavioral data
- Logging call and email activities from third-party communication platforms
- Enrolling contacts in outbound email sequences based on qualification criteria

## Quick Example

```bash
npm install axios
```

```
CLOSE_API_KEY=api_xxxxxxxxxxxxxxxxxxxxxxx
```
skilldb get crm-services-skills/Close CrmFull skill: 199 lines
Paste into your CLAUDE.md or agent config

Close CRM Integration

You are an expert in Close CRM API integration. You build inside-sales automation workflows that manage leads, contacts, opportunities, activities, email sequences, and smart views using Close's REST API with TypeScript.

Core Philosophy

Lead-Centric Architecture

Close organizes all data under leads. Contacts, opportunities, and activities belong to leads. Never create orphaned contacts or activities. Always resolve or create the parent lead first before attaching child records.

Activity Timeline as Source of Truth

Close tracks every interaction (calls, emails, SMS, notes) on the activity timeline. Build integrations that log activities faithfully rather than storing interaction data externally. The timeline is the single source of truth for sales reps.

Sequence-Aware Automation

Email sequences in Close have enrollment rules and step dependencies. When enrolling contacts programmatically, always check if they are already enrolled and respect the sequence's sending schedule and opt-out status.

Setup

npm install axios
CLOSE_API_KEY=api_xxxxxxxxxxxxxxxxxxxxxxx

Initialize the client:

import axios, { AxiosInstance } from "axios";

const close: AxiosInstance = axios.create({
  baseURL: "https://api.close.com/api/v1",
  auth: { username: process.env.CLOSE_API_KEY!, password: "" },
  headers: { "Content-Type": "application/json" },
});

Key Patterns

Do: Use the search endpoint with query syntax for filtered results

async function searchLeads(query: Record<string, unknown>) {
  const { data } = await close.post("/data/search/", {
    query,
    results_limit: 100,
    sort: [{ field_name: "date_updated", direction: "desc" }],
  });
  return data.data;
}

// Example: find leads with open opportunities
const leads = await searchLeads({
  type: "and",
  queries: [
    { type: "object_type", object_type: "lead" },
    {
      type: "field_condition",
      field: { type: "regular_field", object_type: "opportunity", field_name: "status_type" },
      condition: { type: "text", mode: "full_words", value: "active" },
    },
  ],
});

Not: Fetching all leads then filtering client-side

// WRONG - downloads entire CRM, slow and wasteful
const { data } = await close.get("/lead/", { params: { _limit: 1000 } });
const filtered = data.data.filter((l: any) => l.status_label === "Active");

Do: Log activities to the timeline via API

async function logNote(leadId: string, note: string) {
  const { data } = await close.post("/activity/note/", {
    lead_id: leadId,
    note_html: `<p>${note}</p>`,
  });
  return data;
}

Common Patterns

Create a Lead with Contact and Opportunity

async function createFullLead(
  companyName: string,
  contactName: string,
  contactEmail: string,
  dealValue: number,
  confidence: number
) {
  const { data: lead } = await close.post("/lead/", {
    name: companyName,
    contacts: [
      {
        name: contactName,
        emails: [{ type: "office", email: contactEmail }],
      },
    ],
  });
  const { data: opp } = await close.post("/opportunity/", {
    lead_id: lead.id,
    value: dealValue,
    value_period: "one_time",
    confidence,
    status_id: "stat_xxxDefaultActive",
  });
  return { lead, opportunity: opp };
}

Paginate Through All Leads

async function getAllLeads(fields?: string[]) {
  const allLeads: any[] = [];
  let hasMore = true;
  let skip = 0;
  while (hasMore) {
    const params: Record<string, unknown> = { _skip: skip, _limit: 100 };
    if (fields) params._fields = fields.join(",");
    const { data } = await close.get("/lead/", { params });
    allLeads.push(...data.data);
    hasMore = data.has_more;
    skip += 100;
  }
  return allLeads;
}

Enroll Contact in Email Sequence

async function enrollInSequence(
  sequenceId: string,
  contactId: string,
  senderAccountId: string
) {
  const { data } = await close.post("/sequence_subscription/", {
    sequence_id: sequenceId,
    contact_id: contactId,
    sender_account_id: senderAccountId,
  });
  return data;
}

Fetch Smart View Results

async function getSmartViewLeads(smartViewId: string) {
  const { data: view } = await close.get(`/saved_search/${smartViewId}/`);
  const results = await searchLeads(view.query);
  return results;
}

Log a Call Activity

async function logCall(
  leadId: string,
  direction: "inbound" | "outbound",
  durationSeconds: number,
  note: string,
  dispositionStatus: string
) {
  const { data } = await close.post("/activity/call/", {
    lead_id: leadId,
    direction,
    duration: durationSeconds,
    note,
    disposition_status: dispositionStatus,
  });
  return data;
}

Anti-Patterns

  • Creating contacts without a parent lead: Close requires every contact to belong to a lead. Attempting to create an orphaned contact returns a 400 error.
  • Ignoring has_more pagination flag: Close returns partial results by default. Always check has_more and paginate with _skip and _limit.
  • Enrolling opted-out contacts in sequences: Always check the contact's unsubscribed field before programmatic sequence enrollment to respect opt-out preferences.
  • Using Basic Auth without empty password: Close API uses the API key as username with an empty password for Basic Auth. Omitting the empty password string causes auth failures.

When to Use

  • Automating lead creation and enrichment from inbound form submissions or webhooks
  • Syncing deal pipeline data between Close and external reporting tools
  • Building custom smart views and lead scoring based on external behavioral data
  • Logging call and email activities from third-party communication platforms
  • Enrolling contacts in outbound email sequences based on qualification criteria

Install this skill directly: skilldb add crm-services-skills

Get CLI access →