Connectors
/
Close
Live · 81 tools

Close Integration for AI Agents

The Close API is well-documented. Getting your agent to manage real sales reps' pipelines — across multiple Close organizations, with per-user token isolation and seamless token refresh — is the part that compounds fast.
Close
Live

CRM

Sales

Status
Live
Tools
81 pre-built
Auth
OAuth 2.0
Credential storage
Sandbox support

OAuth 2.0

Auto token refresh

Revocation detection

Per-user isolation

The real problem

Why this is harder than it looks

The Close REST API is clean and well-structured. You can get a prototype querying leads and logging calls against your own account in an afternoon. The complexity hits when you try to run this for real users in a multi-tenant product.

Close uses OAuth 2.0 with access tokens that expire after one hour. In a single-user context, that's trivial to handle. In a multi-tenant product where each of your customers' sales reps connects their own Close account, you need per-user token storage that is securely isolated across tenants — one rep's tokens must never be accessible to another, even within the same organization. You also need refresh logic that runs proactively before expiry, not reactively after a failed call that a rep is waiting on. If a user revokes access in Close, you need to detect it and surface a re-authorization prompt rather than silently returning errors.

On top of that, Close's OAuth app setup requires you to register your own OAuth application, supply a redirect URI, and store your own Client ID and Secret — there's no shared managed app you can borrow. That's fine in isolation, but it means your codebase is now managing OAuth app credentials, per-user access tokens, per-user refresh tokens, and all the failure modes that come with them — before you've written a single lead query.

Scalekit handles the OAuth flow, per-user token storage, automatic refresh, and revocation detection. Your agent names a tool and passes parameters. The auth plumbing is not your problem.

Capabilities

What your agent can do with Close

Once connected, your agent has 81 pre-built tools covering the full Close CRM object model:

  • Manage leads end-to-end: create, retrieve, update, delete, merge, and search leads with full-text query support
  • Track contacts and opportunities: create contacts with email and phone, manage deals through pipeline stages with value and confidence tracking
  • Log all activity types: record calls, emails, SMS, and notes on leads; update statuses and link to contacts
  • Automate with sequences: enroll contacts in email sequences, monitor subscription state, pause or resume enrollment
  • Manage tasks and follow-ups: create, assign, and complete tasks with due dates; filter by view, type, or assignee
  • Inspect org structure: list users, pipelines, custom fields, and webhook subscriptions
Setup context

What we're building

This guide connects a sales assistant agent to Close — helping reps query their pipeline, log activities, and manage leads without leaving your product.

🤖
Example agent
Sales assistant querying leads, logging calls and notes, and managing pipeline opportunities on behalf of each rep
🔐
Auth model
B2B SaaS — each sales rep connects their own Close account. identifier = your user ID
⚙️
Scalekit account
app.scalekit.com — Client ID, Secret, Env URL
🔑
Close OAuth app
Register an OAuth app in Close at Settings → Developer → OAuth Apps. Copy the Client ID and Secret.
Setup

1 Setup: One SDK, One credential

Install the Scalekit SDK. The only credential your application manages is the Scalekit API key — no Close secrets, no user tokens, nothing belonging to your users.

pip install scalekit-sdk-python
npm install @scalekit-sdk/node
import scalekit.client import os from dotenv import load_dotenv load_dotenv() scalekit = scalekit.client.ScalekitClient( client_id=os.getenv("SCALEKIT_CLIENT_ID"), client_secret=os.getenv("SCALEKIT_CLIENT_SECRET"), env_url=os.getenv("SCALEKIT_ENV_URL"), ) actions = scalekit.actions
import { ScalekitClient } from '@scalekit-sdk/node'; import 'dotenv/config'; const scalekit = new ScalekitClient( process.env.SCALEKIT_ENV_URL, process.env.SCALEKIT_CLIENT_ID, process.env.SCALEKIT_CLIENT_SECRET ); const actions = scalekit.actions;
Already have credentials?
Connected Accounts

2 Per-User Auth: Creating connected accounts

Each sales rep gets their own Connected Account. The identifier is any unique string from your system — a UUID, email, or whatever you use internally.

response = actions.get_or_create_connected_account( connection_name="close", identifier="user_close_123" # your internal user ID ) connected_account = response.connected_account print(f"Status: {connected_account.status}") # Status: PENDING — user hasn't authorized yet
const response = await actions.getOrCreateConnectedAccount({ connectionName: "close", identifier: "user_close_123" // your internal user ID }); const connectedAccount = response.connectedAccount; console.log(`Status: ${connectedAccount.status}`); // Status: PENDING — user hasn't authorized yet

This call is idempotent — safe to call on every session start. Returns the existing account if one already exists.

Authorization Flow

3 The authorization flow

The rep authorizes once. Scalekit generates the OAuth URL with the correct scopes and redirect handling pre-configured. After approval, you never see the token.

if connected_account.status != "ACTIVE": link = actions.get_authorization_link( connection_name="close", identifier="user_close_123" ) # Redirect user → Close's native OAuth consent screen # Scalekit captures the token on callback return redirect(link.link)
if (connectedAccount.status !== "ACTIVE") { const { link } = await actions.getAuthorizationLink({ connectionName: "close", identifier: "user_close_123" }); // Redirect user → Close's native OAuth consent screen // Scalekit captures the token on callback return redirect(link); }
Token management is automatic
After the user approves, Scalekit stores encrypted tokens and the connected account moves to ACTIVE. Close access tokens expire after 1 hour — Scalekit refreshes them automatically using the offline_access refresh token. If a user revokes access in Close, the account moves to REVOKED. No silent failures. Check account.status before critical operations.
Bring Your Own Credentials — required
Close requires you to register your own OAuth app and supply your Client ID and Secret. In the Scalekit dashboard, go to AgentKit → Connections → your Close connection and enter your credentials. Token management stays fully handled by Scalekit.
Calling Close

4 Calling Close: What your agent writes

With the connected account active, your agent calls Close actions using execute_tool(). Name the tool, pass parameters. Scalekit handles token retrieval and request construction.

Search and retrieve leads

Search leads by keyword or list all leads with sorting and pagination. Use query for full-text search — the same syntax as the Close search bar.

result = actions.execute_tool( identifier="user_close_123", tool_name="close_leads_list", tool_input={ "query": "Acme Corp", "_limit": 10, "_order_by": "-date_updated" } ) # Returns: list of leads with id, name, status, contacts, and opportunities
const result = await actions.executeTool({ identifier: "user_close_123", toolName: "close_leads_list", toolInput: { "query": "Acme Corp", "_limit": 10, "_order_by": "-date_updated" } }); // Returns: list of leads with id, name, status, contacts, and opportunities

Create a lead with an opportunity

Create a lead, then attach an opportunity to it. status_id must be a valid pipeline status ID — fetch available statuses with close_pipelines_list first. Monetary values are in cents.

# 1. Create the lead lead = actions.execute_tool( identifier="user_close_123", tool_name="close_lead_create", tool_input={ "name": "Acme Corp", "description": "Inbound from demo request" } ) lead_id = lead["id"] # 2. Get a pipeline status pipelines = actions.execute_tool( identifier="user_close_123", tool_name="close_pipelines_list", tool_input={} ) active_status = next( s for s in pipelines["data"][0]["statuses"] if s["type"] == "active" ) # 3. Create the opportunity opportunity = actions.execute_tool( identifier="user_close_123", tool_name="close_opportunity_create", tool_input={ "lead_id": lead_id, "status_id": active_status["id"], "value": 1200000, # $12,000 in cents "value_currency": "USD", "value_period": "annual", "confidence": 40 } )
// 1. Create the lead const lead = await actions.executeTool({ identifier: "user_close_123", toolName: "close_lead_create", toolInput: { "name": "Acme Corp", "description": "Inbound from demo request" } }); const leadId = lead.id; // 2. Get a pipeline status const pipelines = await actions.executeTool({ identifier: "user_close_123", toolName: "close_pipelines_list", toolInput: {} }); const activeStatus = pipelines.data[0].statuses.find(s => s.type === "active"); // 3. Create the opportunity const opportunity = await actions.executeTool({ identifier: "user_close_123", toolName: "close_opportunity_create", toolInput: { "lead_id": leadId, "status_id": activeStatus.id, "value": 1200000, "value_currency": "USD", "value_period": "annual", "confidence": 40 } });

Log a call and create a follow-up task

Record a completed call on a lead, then create a follow-up task. status on the call reflects the outcome — completed, no_answer, left_voicemail, etc.

from datetime import date, timedelta # Log the call actions.execute_tool( identifier="user_close_123", tool_name="close_call_create", tool_input={ "lead_id": lead_id, "status": "completed", "direction": "outbound", "duration": 840, # seconds "note": "Discussed pricing. Needs legal review before signing." } ) # Schedule a follow-up task tomorrow = (date.today() + timedelta(days=1)).isoformat() actions.execute_tool( identifier="user_close_123", tool_name="close_task_create", tool_input={ "lead_id": lead_id, "text": "Follow up on legal review", "date": tomorrow } )
// Log the call await actions.executeTool({ identifier: "user_close_123", toolName: "close_call_create", toolInput: { "lead_id": leadId, "status": "completed", "direction": "outbound", "duration": 840, "note": "Discussed pricing. Needs legal review before signing." } }); // Schedule a follow-up task const tomorrow = new Date(); tomorrow.setDate(tomorrow.getDate() + 1); await actions.executeTool({ identifier: "user_close_123", toolName: "close_task_create", toolInput: { "lead_id": leadId, "text": "Follow up on legal review", "date": tomorrow.toISOString().split("T")[0] } });

Enroll a contact in a sequence

Enroll a contact in an email sequence. Retrieve available sequences first with close_sequences_list. You can pause or resume enrollment with close_sequence_subscription_update.

# Get available sequences sequences = actions.execute_tool( identifier="user_close_123", tool_name="close_sequences_list", tool_input={"_limit": 5} ) # Enroll the contact subscription = actions.execute_tool( identifier="user_close_123", tool_name="close_sequence_subscription_create", tool_input={ "contact_id": "cont_abc123", "sequence_id": sequences["data"][0]["id"] } ) # Returns: subscription ID, status, and next send date
// Get available sequences const sequences = await actions.executeTool({ identifier: "user_close_123", toolName: "close_sequences_list", toolInput: { "_limit": 5 } }); // Enroll the contact const subscription = await actions.executeTool({ identifier: "user_close_123", toolName: "close_sequence_subscription_create", toolInput: { "contact_id": "cont_abc123", "sequence_id": sequences.data[0].id } }); // Returns: subscription ID, status, and next send date
Framework wiring

5 Wiring into your agent framework

Scalekit integrates directly with LangChain. The agent decides what to look up or act on; Scalekit handles auth on every invocation. No token plumbing in your agent logic.

from langchain_anthropic import ChatAnthropic from langchain.agents import AgentExecutor, create_tool_calling_agent from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder from scalekit.langchain import get_tools close_tools = get_tools( connection_name="close", identifier="user_close_123" ) prompt = ChatPromptTemplate.from_messages([ ("system", "You are a sales assistant. Use the available tools to help manage Close CRM — query leads, log activities, and update pipeline opportunities."), MessagesPlaceholder("chat_history", optional=True), ("human", "{input}"), MessagesPlaceholder("agent_scratchpad"), ]) agent = create_tool_calling_agent(ChatAnthropic(model="claude-sonnet-4-6"), close_tools, prompt) result = AgentExecutor(agent=agent, tools=close_tools).invoke({ "input": "Find all open opportunities over $10k, summarize them, and create a follow-up task for each one due tomorrow" })
import { ChatAnthropic } from "@langchain/anthropic"; import { AgentExecutor, createToolCallingAgent } from "langchain/agents"; import { ChatPromptTemplate, MessagesPlaceholder } from "@langchain/core/prompts"; import { getTools } from "@scalekit-sdk/langchain"; const closeTools = getTools({ connectionName: "close", identifier: "user_close_123" }); const prompt = ChatPromptTemplate.fromMessages([ ["system", "You are a sales assistant. Use the available tools to help manage Close CRM — query leads, log activities, and update pipeline opportunities."], new MessagesPlaceholder("chat_history", true), ["human", "{input}"], new MessagesPlaceholder("agent_scratchpad"), ]); const agent = await createToolCallingAgent({ llm: new ChatAnthropic({ model: "claude-sonnet-4-6" }), tools: closeTools, prompt }); const result = await AgentExecutor.fromAgentAndTools({ agent, tools: closeTools }).invoke({ input: "Find all open opportunities over $10k, summarize them, and create a follow-up task for each one due tomorrow" });
Other frameworks supported
Tool reference

All 81 Close tools

Grouped by object and capability. Your agent calls tools by name — no API wrappers to write.

Leads
close_lead_create
Create a new lead with name, description, status, and website URL
close_lead_get
Retrieve a single lead by ID with optional field selection
close_lead_update
Update a lead's name, status, description, or website URL
close_lead_delete
Permanently delete a lead and all associated data
close_lead_merge
Merge two leads — source is merged into destination and deleted
close_leads_list
List and full-text search leads with sorting and pagination
Contacts
close_contact_create
Create a contact on a lead with name, title, emails, and phone numbers
close_contact_get
Retrieve a single contact by ID
close_contact_update
Update a contact's name, title, email addresses, or phone numbers
close_contact_delete
Delete a contact from Close
close_contacts_list
List contacts, optionally filtered by lead ID
Opportunities
close_opportunity_create
Create a deal with pipeline status, monetary value (in cents), currency, period, and confidence score
close_opportunity_get
Retrieve a single opportunity by ID
close_opportunity_update
Update status, value, expected close date, confidence, or note
close_opportunity_delete
Delete an opportunity from Close
close_opportunities_list
List opportunities filtered by lead, user, status ID, or status type (active, won, lost)
Activities — Calls
close_call_create
Log an external call with direction, duration, status, and notes
close_call_get
Retrieve a single call activity by ID
close_call_update
Update a call's note, status, or duration
close_call_delete
Delete a call activity
close_calls_list
List calls filtered by lead, contact, or user
Activities — Emails
close_email_create
Log or send an email with subject, HTML/text body, sender, recipients, and status
close_email_get
Retrieve a single email activity by ID
close_email_update
Update an email's status, subject, or body
close_email_delete
Delete an email activity
close_emails_list
List email activities filtered by lead, contact, or user
Activities — SMS
close_sms_create
Log or send an SMS with local/remote phone numbers, body text, and status
close_sms_get
Retrieve a single SMS activity by ID
close_sms_update
Update an SMS activity's text or status
close_sms_delete
Delete an SMS activity
close_sms_list
List SMS activities filtered by lead, contact, or user
Notes & Comments
close_note_create
Create a plain-text note on a lead, optionally linked to a contact
close_note_get
Retrieve a single note by ID
close_note_update
Update the body text of a note
close_note_delete
Delete a note activity
close_notes_list
List notes filtered by lead, contact, or user
close_comment_create
Post a comment on any Close object (lead, opportunity, etc.)
close_comment_get
Retrieve a single comment by ID
close_comment_update
Update the text of an existing comment
close_comment_delete
Delete a comment
close_comments_list
List comments on an object, filtered by object_id or thread_id
Tasks
close_task_create
Create a task with due date, assigned user, and type — linked to a lead
close_task_get
Retrieve a single task by ID
close_task_update
Update task text, assignee, due date, or mark as complete
close_task_delete
Delete a task
close_tasks_list
List tasks filtered by lead, assignee, type, or completion status. Supports inbox, future, and archive views
Sequences
close_sequences_list
List email and activity sequences in the Close organization
close_sequence_get
Retrieve a single sequence by ID
close_sequence_subscription_create
Enroll a contact in a sequence, optionally specifying a sender account
close_sequence_subscription_get
Retrieve a single sequence subscription by ID
close_sequence_subscription_update
Pause or resume a contact's active sequence subscription
close_sequence_subscriptions_list
List sequence subscriptions filtered by lead, contact, or sequence ID
Pipelines
close_pipeline_create
Create a new opportunity pipeline
close_pipeline_get
Retrieve a single pipeline with its statuses by ID
close_pipeline_update
Update a pipeline's name or statuses
close_pipeline_delete
Delete a pipeline
close_pipelines_list
List all pipelines in the organization including their statuses — use this to get valid status_id values
Custom Fields
close_custom_field_lead_create
Create a custom field for leads (text, number, date, url, choices, etc.)
close_custom_field_lead_get
Retrieve a single lead custom field by ID
close_custom_field_lead_update
Update a lead custom field's name or choices
close_custom_field_lead_delete
Delete a lead custom field
close_custom_fields_lead_list
List all custom fields defined for leads
close_custom_field_contact_create
Create a custom field for contacts
close_custom_field_contact_get
Retrieve a single contact custom field by ID
close_custom_field_contact_update
Update a contact custom field's name or choices
close_custom_field_contact_delete
Delete a contact custom field
close_custom_fields_contact_list
List all custom fields defined for contacts
close_custom_field_opportunity_create
Create a custom field for opportunities
close_custom_field_opportunity_get
Retrieve a single opportunity custom field by ID
close_custom_field_opportunity_update
Update an opportunity custom field's name or choices
close_custom_field_opportunity_delete
Delete an opportunity custom field
close_custom_fields_opportunity_list
List all custom fields defined for opportunities
Users, Webhooks & Activities
close_me_get
Retrieve the authenticated user's profile
close_user_get
Retrieve any user by ID
close_users_list
List all users in the Close organization
close_webhook_create
Subscribe to Close events by URL and event type array
close_webhook_get
Retrieve a single webhook subscription by ID
close_webhook_update
Update a webhook's URL or event subscriptions
close_webhook_delete
Delete a webhook subscription
close_webhooks_list
List all webhook subscriptions in the account
close_activities_list
List all activity types for a lead (calls, emails, notes, SMS, etc.) with optional type and user filter
Connector notes

Close-specific behavior

Opportunity values are in cents, not dollars
Close stores monetary values as integers in the smallest currency unit (cents for USD). A $12,000 deal is value: 1200000. Passing a dollar-denominated value silently creates an incorrectly small opportunity. Always convert before writing, and divide by 100 when displaying to users.
status_id is required to create opportunities — it is not a string label
Opportunity creation requires a status_id from a pipeline's statuses array — not a human-readable label like "In Progress". Call close_pipelines_list first to retrieve valid status IDs for the organization. Passing a freeform string returns a validation error.
Scopes are granted automatically — no configuration needed
Close OAuth apps automatically receive all.full_access and offline_access. You do not need to configure scopes in the Scalekit dashboard or request them explicitly. All 81 tools work with the default grant.
Infrastructure decision

Why not build this yourself

The Close OAuth flow is documented. Token storage isn't technically hard. But here's what you're actually signing up for:

PROBLEM 01
Access tokens expire every hour — proactive refresh logic per user, not reactive retries, is required to avoid surfacing errors to reps mid-workflow
PROBLEM 02
Per-user token isolation in a multi-tenant system — one rep's Close credentials must never be accessible to another, even within the same customer org
PROBLEM 03
Revocation detection without OAuth lifecycle hooks — when a user disconnects your app in Close, you need per-account status tracking to surface a re-authorization prompt rather than returning silent 401s
PROBLEM 04
Encrypted credential storage, connected account lifecycle management, and BYOC setup — all required before you write a single lead query

That's one connector. Your agent product will eventually need Salesforce, HubSpot, Zendesk, Gmail, and whatever else your customers ask for. Each has its own auth quirks and failure modes.

Scalekit maintains every connector. You maintain none of them.

Ready to ship

Ship Close in minutes

Free to start. OAuth fully handled.