Close Crm
Integrate with Close CRM API for managing leads, contacts, activities,
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 linesClose 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_morepagination flag: Close returns partial results by default. Always checkhas_moreand paginate with_skipand_limit. - Enrolling opted-out contacts in sequences: Always check the contact's
unsubscribedfield 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
Related Skills
Airtable
Integrate with Airtable API for managing bases, tables, records, views,
Attio
Integrate with Attio CRM API for managing objects, records, lists, notes,
Folk
Integrate with Folk CRM API for managing contacts, pipelines, groups,
Hubspot
Integrate with HubSpot CRM API for managing contacts, deals, companies,
Monday Com
Integrate with Monday.com GraphQL API for managing boards, items, columns,
Pipedrive
Integrate with Pipedrive CRM API for managing deals, persons, organizations,