UNPKG

ghl-mcp-server

Version:

GoHighLevel MCP Server for Claude Desktop and ChatGPT integration

1,494 lines 172 kB
"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(`