ghl-mcp-server
Version:
GoHighLevel MCP Server for Claude Desktop and ChatGPT integration
1,494 lines • 172 kB
JavaScript
"use strict";
/**
* GoHighLevel API Client
* Implements exact API endpoints from OpenAPI specifications v2021-07-28 (Contacts) and v2021-04-15 (Conversations)
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.GHLApiClient = void 0;
const axios_1 = __importDefault(require("axios"));
/**
* GoHighLevel API Client
* Handles all API communication with GHL services
*/
class GHLApiClient {
axiosInstance;
config;
constructor(config) {
this.config = config;
// Create axios instance with base configuration
this.axiosInstance = axios_1.default.create({
baseURL: config.baseUrl,
headers: {
'Authorization': `Bearer ${config.accessToken}`,
'Version': config.version,
'Content-Type': 'application/json',
'Accept': 'application/json'
},
timeout: 30000 // 30 second timeout
});
// Add request interceptor for logging
this.axiosInstance.interceptors.request.use((config) => {
process.stderr.write(`[GHL API] ${config.method?.toUpperCase()} ${config.url}\n`);
return config;
}, (error) => {
console.error('[GHL API] Request error:', error);
return Promise.reject(error);
});
// Add response interceptor for error handling
this.axiosInstance.interceptors.response.use((response) => {
process.stderr.write(`[GHL API] Response ${response.status}: ${response.config.url}\n`);
return response;
}, (error) => {
console.error('[GHL API] Response error:', {
status: error.response?.status,
message: error.response?.data?.message,
url: error.config?.url
});
return Promise.reject(this.handleApiError(error));
});
}
/**
* Handle API errors and convert to standardized format
*/
handleApiError(error) {
const status = error.response?.status || 500;
const message = error.response?.data?.message || error.message || 'Unknown error';
const errorMessage = Array.isArray(message) ? message.join(', ') : message;
return new Error(`GHL API Error (${status}): ${errorMessage}`);
}
/**
* Wrap API responses in standardized format
*/
wrapResponse(data) {
return {
success: true,
data
};
}
/**
* Create custom headers for different API versions
*/
getConversationHeaders() {
return {
'Authorization': `Bearer ${this.config.accessToken}`,
'Version': '2021-04-15', // Conversations API uses different version
'Content-Type': 'application/json',
'Accept': 'application/json'
};
}
/**
* CONTACTS API METHODS
*/
/**
* Create a new contact
* POST /contacts/
*/
async createContact(contactData) {
try {
// Ensure locationId is set
const payload = {
...contactData,
locationId: contactData.locationId || this.config.locationId
};
const response = await this.axiosInstance.post('/contacts/', payload);
return this.wrapResponse(response.data.contact);
}
catch (error) {
throw error;
}
}
/**
* Get contact by ID
* GET /contacts/{contactId}
*/
async getContact(contactId) {
try {
const response = await this.axiosInstance.get(`/contacts/${contactId}`);
return this.wrapResponse(response.data.contact);
}
catch (error) {
throw error;
}
}
/**
* Update existing contact
* PUT /contacts/{contactId}
*/
async updateContact(contactId, updates) {
try {
const response = await this.axiosInstance.put(`/contacts/${contactId}`, updates);
return this.wrapResponse(response.data.contact);
}
catch (error) {
throw error;
}
}
/**
* Delete contact
* DELETE /contacts/{contactId}
*/
async deleteContact(contactId) {
try {
const response = await this.axiosInstance.delete(`/contacts/${contactId}`);
return this.wrapResponse(response.data);
}
catch (error) {
throw error;
}
}
/**
* Search contacts with advanced filters
* POST /contacts/search
*/
async searchContacts(searchParams) {
try {
// Build minimal request body with only required/supported parameters
// Start with just locationId and pageLimit as per API requirements
const payload = {
locationId: searchParams.locationId || this.config.locationId,
pageLimit: searchParams.limit || 25
};
// Only add optional parameters if they have valid values
if (searchParams.query && searchParams.query.trim()) {
payload.query = searchParams.query.trim();
}
if (searchParams.startAfterId && searchParams.startAfterId.trim()) {
payload.startAfterId = searchParams.startAfterId.trim();
}
if (searchParams.startAfter && typeof searchParams.startAfter === 'number') {
payload.startAfter = searchParams.startAfter;
}
// Only add filters if we have valid filter values
if (searchParams.filters) {
const filters = {};
let hasFilters = false;
if (searchParams.filters.email && searchParams.filters.email.trim()) {
filters.email = searchParams.filters.email.trim();
hasFilters = true;
}
if (searchParams.filters.phone && searchParams.filters.phone.trim()) {
filters.phone = searchParams.filters.phone.trim();
hasFilters = true;
}
if (searchParams.filters.tags && Array.isArray(searchParams.filters.tags) && searchParams.filters.tags.length > 0) {
filters.tags = searchParams.filters.tags;
hasFilters = true;
}
if (searchParams.filters.dateAdded && typeof searchParams.filters.dateAdded === 'object') {
filters.dateAdded = searchParams.filters.dateAdded;
hasFilters = true;
}
// Only add filters object if we have actual filters
if (hasFilters) {
payload.filters = filters;
}
}
process.stderr.write(`[GHL API] Search contacts payload: ${JSON.stringify(payload, null, 2)}\n`);
const response = await this.axiosInstance.post('/contacts/search', payload);
return this.wrapResponse(response.data);
}
catch (error) {
const axiosError = error;
process.stderr.write(`[GHL API] Search contacts error: ${JSON.stringify({
status: axiosError.response?.status,
statusText: axiosError.response?.statusText,
data: axiosError.response?.data,
message: axiosError.message
}, null, 2)}\n`);
const handledError = this.handleApiError(axiosError);
return {
success: false,
error: {
message: handledError.message,
statusCode: axiosError.response?.status || 500,
details: axiosError.response?.data
}
};
}
}
/**
* Get duplicate contact by email or phone
* GET /contacts/search/duplicate
*/
async getDuplicateContact(email, phone) {
try {
const params = {
locationId: this.config.locationId
};
if (email)
params.email = encodeURIComponent(email);
if (phone)
params.number = encodeURIComponent(phone);
const response = await this.axiosInstance.get('/contacts/search/duplicate', { params });
return this.wrapResponse(response.data.contact || null);
}
catch (error) {
throw error;
}
}
/**
* Add tags to contact
* POST /contacts/{contactId}/tags
*/
async addContactTags(contactId, tags) {
try {
const payload = { tags };
const response = await this.axiosInstance.post(`/contacts/${contactId}/tags`, payload);
return this.wrapResponse(response.data);
}
catch (error) {
throw error;
}
}
/**
* Remove tags from contact
* DELETE /contacts/{contactId}/tags
*/
async removeContactTags(contactId, tags) {
try {
const payload = { tags };
const response = await this.axiosInstance.delete(`/contacts/${contactId}/tags`, { data: payload });
return this.wrapResponse(response.data);
}
catch (error) {
throw error;
}
}
/**
* CONVERSATIONS API METHODS
*/
/**
* Search conversations with filters
* GET /conversations/search
*/
async searchConversations(searchParams) {
try {
// Ensure locationId is set
const params = {
...searchParams,
locationId: searchParams.locationId || this.config.locationId
};
const response = await this.axiosInstance.get('/conversations/search', {
params,
headers: this.getConversationHeaders()
});
return this.wrapResponse(response.data);
}
catch (error) {
throw error;
}
}
/**
* Get conversation by ID
* GET /conversations/{conversationId}
*/
async getConversation(conversationId) {
try {
const response = await this.axiosInstance.get(`/conversations/${conversationId}`, { headers: this.getConversationHeaders() });
return this.wrapResponse(response.data);
}
catch (error) {
throw error;
}
}
/**
* Create a new conversation
* POST /conversations/
*/
async createConversation(conversationData) {
try {
// Ensure locationId is set
const payload = {
...conversationData,
locationId: conversationData.locationId || this.config.locationId
};
const response = await this.axiosInstance.post('/conversations/', payload, { headers: this.getConversationHeaders() });
return this.wrapResponse(response.data.conversation);
}
catch (error) {
throw error;
}
}
/**
* Update conversation
* PUT /conversations/{conversationId}
*/
async updateConversation(conversationId, updates) {
try {
// Ensure locationId is set
const payload = {
...updates,
locationId: updates.locationId || this.config.locationId
};
const response = await this.axiosInstance.put(`/conversations/${conversationId}`, payload, { headers: this.getConversationHeaders() });
return this.wrapResponse(response.data.conversation);
}
catch (error) {
throw error;
}
}
/**
* Delete conversation
* DELETE /conversations/{conversationId}
*/
async deleteConversation(conversationId) {
try {
const response = await this.axiosInstance.delete(`/conversations/${conversationId}`, { headers: this.getConversationHeaders() });
return this.wrapResponse(response.data);
}
catch (error) {
throw error;
}
}
/**
* Get messages from a conversation
* GET /conversations/{conversationId}/messages
*/
async getConversationMessages(conversationId, options) {
try {
const params = {};
if (options?.lastMessageId)
params.lastMessageId = options.lastMessageId;
if (options?.limit)
params.limit = options.limit;
if (options?.type)
params.type = options.type;
const response = await this.axiosInstance.get(`/conversations/${conversationId}/messages`, {
params,
headers: this.getConversationHeaders()
});
return this.wrapResponse(response.data);
}
catch (error) {
throw error;
}
}
/**
* Get message by ID
* GET /conversations/messages/{id}
*/
async getMessage(messageId) {
try {
const response = await this.axiosInstance.get(`/conversations/messages/${messageId}`, { headers: this.getConversationHeaders() });
return this.wrapResponse(response.data);
}
catch (error) {
throw error;
}
}
/**
* Send a new message (SMS, Email, etc.)
* POST /conversations/messages
*/
async sendMessage(messageData) {
try {
const response = await this.axiosInstance.post('/conversations/messages', messageData, { headers: this.getConversationHeaders() });
return this.wrapResponse(response.data);
}
catch (error) {
throw error;
}
}
/**
* Send SMS message to a contact
* Convenience method for sending SMS
*/
async sendSMS(contactId, message, fromNumber) {
try {
const messageData = {
type: 'SMS',
contactId,
message,
fromNumber
};
return await this.sendMessage(messageData);
}
catch (error) {
throw error;
}
}
/**
* Send Email message to a contact
* Convenience method for sending Email
*/
async sendEmail(contactId, subject, message, html, options) {
try {
const messageData = {
type: 'Email',
contactId,
subject,
message,
html,
...options
};
return await this.sendMessage(messageData);
}
catch (error) {
throw error;
}
}
/**
* BLOG API METHODS
*/
/**
* Get all blog sites for a location
* GET /blogs/site/all
*/
async getBlogSites(params) {
try {
// Ensure locationId is set
const queryParams = {
locationId: params.locationId || this.config.locationId,
skip: params.skip,
limit: params.limit,
...(params.searchTerm && { searchTerm: params.searchTerm })
};
const response = await this.axiosInstance.get('/blogs/site/all', { params: queryParams });
return this.wrapResponse(response.data);
}
catch (error) {
throw error;
}
}
/**
* Get blog posts for a specific blog
* GET /blogs/posts/all
*/
async getBlogPosts(params) {
try {
// Ensure locationId is set
const queryParams = {
locationId: params.locationId || this.config.locationId,
blogId: params.blogId,
limit: params.limit,
offset: params.offset,
...(params.searchTerm && { searchTerm: params.searchTerm }),
...(params.status && { status: params.status })
};
const response = await this.axiosInstance.get('/blogs/posts/all', { params: queryParams });
return this.wrapResponse(response.data);
}
catch (error) {
throw error;
}
}
/**
* Create a new blog post
* POST /blogs/posts
*/
async createBlogPost(postData) {
try {
// Ensure locationId is set
const payload = {
...postData,
locationId: postData.locationId || this.config.locationId
};
const response = await this.axiosInstance.post('/blogs/posts', payload);
return this.wrapResponse(response.data);
}
catch (error) {
throw error;
}
}
/**
* Update an existing blog post
* PUT /blogs/posts/{postId}
*/
async updateBlogPost(postId, postData) {
try {
// Ensure locationId is set
const payload = {
...postData,
locationId: postData.locationId || this.config.locationId
};
const response = await this.axiosInstance.put(`/blogs/posts/${postId}`, payload);
return this.wrapResponse(response.data);
}
catch (error) {
throw error;
}
}
/**
* Get all blog authors for a location
* GET /blogs/authors
*/
async getBlogAuthors(params) {
try {
// Ensure locationId is set
const queryParams = {
locationId: params.locationId || this.config.locationId,
limit: params.limit,
offset: params.offset
};
const response = await this.axiosInstance.get('/blogs/authors', { params: queryParams });
return this.wrapResponse(response.data);
}
catch (error) {
throw error;
}
}
/**
* Get all blog categories for a location
* GET /blogs/categories
*/
async getBlogCategories(params) {
try {
// Ensure locationId is set
const queryParams = {
locationId: params.locationId || this.config.locationId,
limit: params.limit,
offset: params.offset
};
const response = await this.axiosInstance.get('/blogs/categories', { params: queryParams });
return this.wrapResponse(response.data);
}
catch (error) {
throw error;
}
}
/**
* Check if a URL slug exists (for validation before creating/updating posts)
* GET /blogs/posts/url-slug-exists
*/
async checkUrlSlugExists(params) {
try {
// Ensure locationId is set
const queryParams = {
locationId: params.locationId || this.config.locationId,
urlSlug: params.urlSlug,
...(params.postId && { postId: params.postId })
};
const response = await this.axiosInstance.get('/blogs/posts/url-slug-exists', { params: queryParams });
return this.wrapResponse(response.data);
}
catch (error) {
throw error;
}
}
/**
* TASKS API METHODS
*/
/**
* Get all tasks for a contact
* GET /contacts/{contactId}/tasks
*/
async getContactTasks(contactId) {
try {
const response = await this.axiosInstance.get(`/contacts/${contactId}/tasks`);
return this.wrapResponse(response.data.tasks);
}
catch (error) {
throw error;
}
}
/**
* Create task for contact
* POST /contacts/{contactId}/tasks
*/
async createContactTask(contactId, taskData) {
try {
const response = await this.axiosInstance.post(`/contacts/${contactId}/tasks`, taskData);
return this.wrapResponse(response.data.task);
}
catch (error) {
throw error;
}
}
/**
* NOTES API METHODS
*/
/**
* Get all notes for a contact
* GET /contacts/{contactId}/notes
*/
async getContactNotes(contactId) {
try {
const response = await this.axiosInstance.get(`/contacts/${contactId}/notes`);
return this.wrapResponse(response.data.notes);
}
catch (error) {
throw error;
}
}
/**
* Create note for contact
* POST /contacts/{contactId}/notes
*/
async createContactNote(contactId, noteData) {
try {
const response = await this.axiosInstance.post(`/contacts/${contactId}/notes`, noteData);
return this.wrapResponse(response.data.note);
}
catch (error) {
throw error;
}
}
/**
* ADDITIONAL CONTACT API METHODS
*/
/**
* Get a specific task for a contact
* GET /contacts/{contactId}/tasks/{taskId}
*/
async getContactTask(contactId, taskId) {
try {
const response = await this.axiosInstance.get(`/contacts/${contactId}/tasks/${taskId}`);
return this.wrapResponse(response.data.task);
}
catch (error) {
throw this.handleApiError(error);
}
}
/**
* Update a task for a contact
* PUT /contacts/{contactId}/tasks/{taskId}
*/
async updateContactTask(contactId, taskId, updates) {
try {
const response = await this.axiosInstance.put(`/contacts/${contactId}/tasks/${taskId}`, updates);
return this.wrapResponse(response.data.task);
}
catch (error) {
throw this.handleApiError(error);
}
}
/**
* Delete a task for a contact
* DELETE /contacts/{contactId}/tasks/{taskId}
*/
async deleteContactTask(contactId, taskId) {
try {
const response = await this.axiosInstance.delete(`/contacts/${contactId}/tasks/${taskId}`);
return this.wrapResponse(response.data);
}
catch (error) {
throw this.handleApiError(error);
}
}
/**
* Update task completion status
* PUT /contacts/{contactId}/tasks/{taskId}/completed
*/
async updateTaskCompletion(contactId, taskId, completed) {
try {
const response = await this.axiosInstance.put(`/contacts/${contactId}/tasks/${taskId}/completed`, { completed });
return this.wrapResponse(response.data.task);
}
catch (error) {
throw this.handleApiError(error);
}
}
/**
* Get a specific note for a contact
* GET /contacts/{contactId}/notes/{noteId}
*/
async getContactNote(contactId, noteId) {
try {
const response = await this.axiosInstance.get(`/contacts/${contactId}/notes/${noteId}`);
return this.wrapResponse(response.data.note);
}
catch (error) {
throw this.handleApiError(error);
}
}
/**
* Update a note for a contact
* PUT /contacts/{contactId}/notes/{noteId}
*/
async updateContactNote(contactId, noteId, updates) {
try {
const response = await this.axiosInstance.put(`/contacts/${contactId}/notes/${noteId}`, updates);
return this.wrapResponse(response.data.note);
}
catch (error) {
throw this.handleApiError(error);
}
}
/**
* Delete a note for a contact
* DELETE /contacts/{contactId}/notes/{noteId}
*/
async deleteContactNote(contactId, noteId) {
try {
const response = await this.axiosInstance.delete(`/contacts/${contactId}/notes/${noteId}`);
return this.wrapResponse(response.data);
}
catch (error) {
throw this.handleApiError(error);
}
}
/**
* Upsert contact (create or update based on email/phone)
* POST /contacts/upsert
*/
async upsertContact(contactData) {
try {
const payload = {
...contactData,
locationId: contactData.locationId || this.config.locationId
};
const response = await this.axiosInstance.post('/contacts/upsert', payload);
return this.wrapResponse(response.data);
}
catch (error) {
throw this.handleApiError(error);
}
}
/**
* Get contacts by business ID
* GET /contacts/business/{businessId}
*/
async getContactsByBusiness(businessId, params = {}) {
try {
const queryParams = {
limit: params.limit || 25,
skip: params.skip || 0,
...(params.query && { query: params.query })
};
const response = await this.axiosInstance.get(`/contacts/business/${businessId}`, { params: queryParams });
return this.wrapResponse(response.data);
}
catch (error) {
throw this.handleApiError(error);
}
}
/**
* Get contact appointments
* GET /contacts/{contactId}/appointments
*/
async getContactAppointments(contactId) {
try {
const response = await this.axiosInstance.get(`/contacts/${contactId}/appointments`);
return this.wrapResponse(response.data.events);
}
catch (error) {
throw this.handleApiError(error);
}
}
/**
* Bulk update contact tags
* POST /contacts/tags/bulk
*/
async bulkUpdateContactTags(contactIds, tags, operation, removeAllTags) {
try {
const payload = {
ids: contactIds,
tags,
operation,
...(removeAllTags !== undefined && { removeAllTags })
};
const response = await this.axiosInstance.post('/contacts/tags/bulk', payload);
return this.wrapResponse(response.data);
}
catch (error) {
throw this.handleApiError(error);
}
}
/**
* Bulk update contact business
* POST /contacts/business/bulk
*/
async bulkUpdateContactBusiness(contactIds, businessId) {
try {
const payload = {
ids: contactIds,
businessId: businessId || null
};
const response = await this.axiosInstance.post('/contacts/business/bulk', payload);
return this.wrapResponse(response.data);
}
catch (error) {
throw this.handleApiError(error);
}
}
/**
* Add contact followers
* POST /contacts/{contactId}/followers
*/
async addContactFollowers(contactId, followers) {
try {
const payload = { followers };
const response = await this.axiosInstance.post(`/contacts/${contactId}/followers`, payload);
return this.wrapResponse(response.data);
}
catch (error) {
throw this.handleApiError(error);
}
}
/**
* Remove contact followers
* DELETE /contacts/{contactId}/followers
*/
async removeContactFollowers(contactId, followers) {
try {
const payload = { followers };
const response = await this.axiosInstance.delete(`/contacts/${contactId}/followers`, { data: payload });
return this.wrapResponse(response.data);
}
catch (error) {
throw this.handleApiError(error);
}
}
/**
* Add contact to campaign
* POST /contacts/{contactId}/campaigns/{campaignId}
*/
async addContactToCampaign(contactId, campaignId) {
try {
const response = await this.axiosInstance.post(`/contacts/${contactId}/campaigns/${campaignId}`);
return this.wrapResponse(response.data);
}
catch (error) {
throw this.handleApiError(error);
}
}
/**
* Remove contact from campaign
* DELETE /contacts/{contactId}/campaigns/{campaignId}
*/
async removeContactFromCampaign(contactId, campaignId) {
try {
const response = await this.axiosInstance.delete(`/contacts/${contactId}/campaigns/${campaignId}`);
return this.wrapResponse(response.data);
}
catch (error) {
throw this.handleApiError(error);
}
}
/**
* Remove contact from all campaigns
* DELETE /contacts/{contactId}/campaigns
*/
async removeContactFromAllCampaigns(contactId) {
try {
const response = await this.axiosInstance.delete(`/contacts/${contactId}/campaigns`);
return this.wrapResponse(response.data);
}
catch (error) {
throw this.handleApiError(error);
}
}
/**
* Add contact to workflow
* POST /contacts/{contactId}/workflow/{workflowId}
*/
async addContactToWorkflow(contactId, workflowId, eventStartTime) {
try {
const payload = eventStartTime ? { eventStartTime } : {};
const response = await this.axiosInstance.post(`/contacts/${contactId}/workflow/${workflowId}`, payload);
return this.wrapResponse(response.data);
}
catch (error) {
throw this.handleApiError(error);
}
}
/**
* Remove contact from workflow
* DELETE /contacts/{contactId}/workflow/{workflowId}
*/
async removeContactFromWorkflow(contactId, workflowId, eventStartTime) {
try {
const payload = eventStartTime ? { eventStartTime } : {};
const response = await this.axiosInstance.delete(`/contacts/${contactId}/workflow/${workflowId}`, { data: payload });
return this.wrapResponse(response.data);
}
catch (error) {
throw this.handleApiError(error);
}
}
/**
* UTILITY METHODS
*/
/**
* Test API connection and authentication
*/
async testConnection() {
try {
// Test with a simple GET request to check API connectivity
const response = await this.axiosInstance.get('/locations/' + this.config.locationId);
return this.wrapResponse({
status: 'connected',
locationId: this.config.locationId
});
}
catch (error) {
throw new Error(`GHL API connection test failed: ${error}`);
}
}
/**
* Update access token
*/
updateAccessToken(newToken) {
this.config.accessToken = newToken;
this.axiosInstance.defaults.headers['Authorization'] = `Bearer ${newToken}`;
process.stderr.write('[GHL API] Access token updated\n');
}
/**
* Get current configuration
*/
getConfig() {
return { ...this.config };
}
/**
* OPPORTUNITIES API METHODS
*/
/**
* Search opportunities with advanced filters
* GET /opportunities/search
*/
async searchOpportunities(searchParams) {
try {
// Build query parameters with exact API naming (underscores)
const params = {
location_id: searchParams.location_id || this.config.locationId
};
// Add optional search parameters only if they have values
if (searchParams.q && searchParams.q.trim()) {
params.q = searchParams.q.trim();
}
if (searchParams.pipeline_id) {
params.pipeline_id = searchParams.pipeline_id;
}
if (searchParams.pipeline_stage_id) {
params.pipeline_stage_id = searchParams.pipeline_stage_id;
}
if (searchParams.contact_id) {
params.contact_id = searchParams.contact_id;
}
if (searchParams.status) {
params.status = searchParams.status;
}
if (searchParams.assigned_to) {
params.assigned_to = searchParams.assigned_to;
}
if (searchParams.campaignId) {
params.campaignId = searchParams.campaignId;
}
if (searchParams.id) {
params.id = searchParams.id;
}
if (searchParams.order) {
params.order = searchParams.order;
}
if (searchParams.endDate) {
params.endDate = searchParams.endDate;
}
if (searchParams.startAfter) {
params.startAfter = searchParams.startAfter;
}
if (searchParams.startAfterId) {
params.startAfterId = searchParams.startAfterId;
}
if (searchParams.date) {
params.date = searchParams.date;
}
if (searchParams.country) {
params.country = searchParams.country;
}
if (searchParams.page) {
params.page = searchParams.page;
}
if (searchParams.limit) {
params.limit = searchParams.limit;
}
if (searchParams.getTasks !== undefined) {
params.getTasks = searchParams.getTasks;
}
if (searchParams.getNotes !== undefined) {
params.getNotes = searchParams.getNotes;
}
if (searchParams.getCalendarEvents !== undefined) {
params.getCalendarEvents = searchParams.getCalendarEvents;
}
process.stderr.write(`[GHL API] Search opportunities params: ${JSON.stringify(params, null, 2)}\n`);
const response = await this.axiosInstance.get('/opportunities/search', { params });
return this.wrapResponse(response.data);
}
catch (error) {
const axiosError = error;
process.stderr.write(`[GHL API] Search opportunities error: ${JSON.stringify({
status: axiosError.response?.status,
statusText: axiosError.response?.statusText,
data: axiosError.response?.data,
message: axiosError.message
}, null, 2)}\n`);
throw this.handleApiError(axiosError);
}
}
/**
* Get all pipelines for a location
* GET /opportunities/pipelines
*/
async getPipelines(locationId) {
try {
const params = {
locationId: locationId || this.config.locationId
};
const response = await this.axiosInstance.get('/opportunities/pipelines', { params });
return this.wrapResponse(response.data);
}
catch (error) {
throw this.handleApiError(error);
}
}
/**
* Get opportunity by ID
* GET /opportunities/{id}
*/
async getOpportunity(opportunityId) {
try {
const response = await this.axiosInstance.get(`/opportunities/${opportunityId}`);
return this.wrapResponse(response.data.opportunity);
}
catch (error) {
throw this.handleApiError(error);
}
}
/**
* Create a new opportunity
* POST /opportunities/
*/
async createOpportunity(opportunityData) {
try {
// Ensure locationId is set
const payload = {
...opportunityData,
locationId: opportunityData.locationId || this.config.locationId
};
const response = await this.axiosInstance.post('/opportunities/', payload);
return this.wrapResponse(response.data.opportunity);
}
catch (error) {
throw this.handleApiError(error);
}
}
/**
* Update existing opportunity
* PUT /opportunities/{id}
*/
async updateOpportunity(opportunityId, updates) {
try {
const response = await this.axiosInstance.put(`/opportunities/${opportunityId}`, updates);
return this.wrapResponse(response.data.opportunity);
}
catch (error) {
throw this.handleApiError(error);
}
}
/**
* Update opportunity status
* PUT /opportunities/{id}/status
*/
async updateOpportunityStatus(opportunityId, status) {
try {
const payload = { status };
const response = await this.axiosInstance.put(`/opportunities/${opportunityId}/status`, payload);
return this.wrapResponse(response.data);
}
catch (error) {
throw this.handleApiError(error);
}
}
/**
* Upsert opportunity (create or update)
* POST /opportunities/upsert
*/
async upsertOpportunity(opportunityData) {
try {
// Ensure locationId is set
const payload = {
...opportunityData,
locationId: opportunityData.locationId || this.config.locationId
};
const response = await this.axiosInstance.post('/opportunities/upsert', payload);
return this.wrapResponse(response.data);
}
catch (error) {
throw this.handleApiError(error);
}
}
/**
* Delete opportunity
* DELETE /opportunities/{id}
*/
async deleteOpportunity(opportunityId) {
try {
const response = await this.axiosInstance.delete(`/opportunities/${opportunityId}`);
return this.wrapResponse(response.data);
}
catch (error) {
throw this.handleApiError(error);
}
}
/**
* Add followers to opportunity
* POST /opportunities/{id}/followers
*/
async addOpportunityFollowers(opportunityId, followers) {
try {
const payload = { followers };
const response = await this.axiosInstance.post(`/opportunities/${opportunityId}/followers`, payload);
return this.wrapResponse(response.data);
}
catch (error) {
throw this.handleApiError(error);
}
}
/**
* Remove followers from opportunity
* DELETE /opportunities/{id}/followers
*/
async removeOpportunityFollowers(opportunityId, followers) {
try {
const payload = { followers };
const response = await this.axiosInstance.delete(`/opportunities/${opportunityId}/followers`, { data: payload });
return this.wrapResponse(response.data);
}
catch (error) {
throw this.handleApiError(error);
}
}
/**
* CALENDAR & APPOINTMENTS API METHODS
*/
/**
* Get all calendar groups in a location
* GET /calendars/groups
*/
async getCalendarGroups(locationId) {
try {
const params = {
locationId: locationId || this.config.locationId
};
const response = await this.axiosInstance.get('/calendars/groups', { params });
return this.wrapResponse(response.data);
}
catch (error) {
throw this.handleApiError(error);
}
}
/**
* Create a new calendar group
* POST /calendars/groups
*/
async createCalendarGroup(groupData) {
try {
const payload = {
...groupData,
locationId: groupData.locationId || this.config.locationId
};
const response = await this.axiosInstance.post('/calendars/groups', payload);
return this.wrapResponse(response.data);
}
catch (error) {
throw this.handleApiError(error);
}
}
/**
* Get all calendars in a location
* GET /calendars/
*/
async getCalendars(params) {
try {
const queryParams = {
locationId: params?.locationId || this.config.locationId,
...(params?.groupId && { groupId: params.groupId }),
...(params?.showDrafted !== undefined && { showDrafted: params.showDrafted })
};
const response = await this.axiosInstance.get('/calendars/', { params: queryParams });
return this.wrapResponse(response.data);
}
catch (error) {
throw this.handleApiError(error);
}
}
/**
* Create a new calendar
* POST /calendars/
*/
async createCalendar(calendarData) {
try {
const payload = {
...calendarData,
locationId: calendarData.locationId || this.config.locationId
};
const response = await this.axiosInstance.post('/calendars/', payload);
return this.wrapResponse(response.data);
}
catch (error) {
throw this.handleApiError(error);
}
}
/**
* Get calendar by ID
* GET /calendars/{calendarId}
*/
async getCalendar(calendarId) {
try {
const response = await this.axiosInstance.get(`/calendars/${calendarId}`);
return this.wrapResponse(response.data);
}
catch (error) {
throw this.handleApiError(error);
}
}
/**
* Update calendar by ID
* PUT /calendars/{calendarId}
*/
async updateCalendar(calendarId, updates) {
try {
const response = await this.axiosInstance.put(`/calendars/${calendarId}`, updates);
return this.wrapResponse(response.data);
}
catch (error) {
throw this.handleApiError(error);
}
}
/**
* Delete calendar by ID
* DELETE /calendars/{calendarId}
*/
async deleteCalendar(calendarId) {
try {
const response = await this.axiosInstance.delete(`/calendars/${calendarId}`);
return this.wrapResponse(response.data);
}
catch (error) {
throw this.handleApiError(error);
}
}
/**
* Get calendar events/appointments
* GET /calendars/events
*/
async getCalendarEvents(eventParams) {
try {
const params = {
locationId: eventParams.locationId || this.config.locationId,
startTime: eventParams.startTime,
endTime: eventParams.endTime,
...(eventParams.userId && { userId: eventParams.userId }),
...(eventParams.calendarId && { calendarId: eventParams.calendarId }),
...(eventParams.groupId && { groupId: eventParams.groupId })
};
const response = await this.axiosInstance.get('/calendars/events', { params });
return this.wrapResponse(response.data);
}
catch (error) {
throw this.handleApiError(error);
}
}
/**
* Get blocked slots
* GET /calendars/blocked-slots
*/
async getBlockedSlots(eventParams) {
try {
const params = {
locationId: eventParams.locationId || this.config.locationId,
startTime: eventParams.startTime,
endTime: eventParams.endTime,
...(eventParams.userId && { userId: eventParams.userId }),
...(eventParams.calendarId && { calendarId: eventParams.calendarId }),
...(eventParams.groupId && { groupId: eventParams.groupId })
};
const response = await this.axiosInstance.get('/calendars/blocked-slots', { params });
return this.wrapResponse(response.data);
}
catch (error) {
throw this.handleApiError(error);
}
}
/**
* Get free slots for a calendar
* GET /calendars/{calendarId}/free-slots
*/
async getFreeSlots(slotParams) {
try {
const params = {
startDate: slotParams.startDate,
endDate: slotParams.endDate,
...(slotParams.timezone && { timezone: slotParams.timezone }),
...(slotParams.userId && { userId: slotParams.userId }),
...(slotParams.userIds && { userIds: slotParams.userIds }),
...(slotParams.enableLookBusy !== undefined && { enableLookBusy: slotParams.enableLookBusy })
};
const response = await this.axiosInstance.get(`/calendars/${slotParams.calendarId}/free-slots`, { params });
return this.wrapResponse(response.data);
}
catch (error) {
throw this.handleApiError(error);
}
}
/**
* Create a new appointment
* POST /calendars/events/appointments
*/
async createAppointment(appointmentData) {
try {
const payload = {
...appointmentData,
locationId: appointmentData.locationId || this.config.locationId
};
const response = await this.axiosInstance.post('/calendars/events/appointments', payload);
return this.wrapResponse(response.data);
}
catch (error) {
throw this.handleApiError(error);
}
}
/**
* Get appointment by ID
* GET /calendars/events/appointments/{eventId}
*/
async getAppointment(appointmentId) {
try {
const response = await this.axiosInstance.get(`/calendars/events/appointments/${appointmentId}`);
return this.wrapResponse(response.data);
}
catch (error) {
throw this.handleApiError(error);
}
}
/**
* Update appointment by ID
* PUT /calendars/events/appointments/{eventId}
*/
async updateAppointment(appointmentId, updates) {
try {
const response = await this.axiosInstance.put(`/calendars/events/appointments/${appointmentId}`, updates);
return this.wrapResponse(response.data);
}
catch (error) {
throw this.handleApiError(error);
}
}
/**
* Delete appointment by ID
* DELETE /calendars/events/appointments/{eventId}
*/
async deleteAppointment(appointmentId) {
try {
const response = await this.axiosInstance.delete(`/calendars/events/appointments/${appointmentId}`);
return this.wrapResponse(response.data);
}
catch (error) {
throw this.handleApiError(error);
}
}
/**
* Update block slot by ID
* PUT /calendars/events/block-slots/{eventId}
*/
async updateBlockSlot(blockSlotId, updates) {
try {
const response = await this.axiosInstance.put(`/calendars/events/block-slots/${blockSlotId}`, updates);
return this.wrapResponse(response.data);
}
catch (error) {
throw this.handleApiError(error);
}
}
/**
* EMAIL API METHODS
*/
async getEmailCampaigns(params) {
try {
const response = await this.axiosInstance.get('/emails/schedule', {
params: {
locationId: this.config.locationId,
...params
}
});
return this.wrapResponse(response.data);
}
catch (error) {
throw this.handleApiError(error);
}
}
async createEmailTemplate(params) {
try {
const response = await this.axiosInstance.post('/emails/builder', {
locationId: this.config.locationId,
type: 'html',
...params
});
return this.wrapResponse(response.data);
}
catch (error) {
throw this.handleApiError(error);
}
}
async getEmailTemplates(params) {
try {
const response = await this.axiosInstance.get('/emails/builder', {
params: {
locationId: this.config.locationId,
...params
}
});
return this.wrapResponse(response.data);
}
catch (error) {
throw this.handleApiError(error);
}
}
async updateEmailTemplate(params) {
try {
const { templateId, ...data } = params;
const response = await this.axiosInstance.post('/emails/builder/data', {
locationId: this.config.locationId,
templateId,
...data,
editorType: 'html'
});
return this.wrapResponse(response.data);
}
catch (error) {
throw this.handleApiError(error);
}
}
async deleteEmailTemplate(params) {
try {
const { templateId } = params;
const response = await this.axiosInstance.delete(`/emails/builder/${this.config.locationId}/${templateId}`);
return this.wrapResponse(response.data);
}
catch (error) {
throw this.handleApiError(error);
}
}
/**
* LOCATION API METHODS
*/
/**
* Search locations/sub-accounts
* GET /locations/search
*/
async searchLocations(params = {}) {
try {
const queryParams = {
skip: params.skip || 0,
limit: params.limit || 10,
order: params.order || 'asc',
...(params.companyId && { companyId: params.companyId }),
...(params.email && { email: params.email })
};
const response = await this.axiosInstance.get('/locations/search', { params: queryParams });
return this.wrapResponse(response.data);
}
catch (error) {
throw this.handleApiError(error);
}
}
/**
* Get location by ID
* GET /locations/{locationId}
*/
async getLocationById(locationId) {
try {
const response = await this.axiosInstance.get(`