diff --git a/HEARTBEAT.md b/HEARTBEAT.md
index aa6a3c4..14170bc 100644
--- a/HEARTBEAT.md
+++ b/HEARTBEAT.md
@@ -15,4 +15,4 @@
Building bulk animation generator for MCP marketing. Using Remotion with canvas viewport technique (dolly camera). Camera should zoom into typing area, follow text as typed, zoom out when done. Category-specific questions per software type.
---
-*Last updated: 2026-01-28 23:00 EST*
+*Last updated: 2026-01-30 23:00 EST*
diff --git a/config/mcporter.json b/config/mcporter.json
index d48a6b9..21079f5 100644
--- a/config/mcporter.json
+++ b/config/mcporter.json
@@ -5,7 +5,7 @@
"command": "node",
"args": ["/Users/jakeshore/.clawdbot/workspace/mcp-diagrams/GoHighLevel-MCP/dist/server.js"],
"env": {
- "GHL_API_KEY": "pit-0aebc49f-07f7-47dc-a494-181b72a1df54",
+ "GHL_API_KEY": "pit-0480982f-750b-4baa-bc10-1340a9b2102b",
"GHL_BASE_URL": "https://services.leadconnectorhq.com",
"GHL_LOCATION_ID": "DZEpRd43MxUJKdtrev9t",
"NODE_ENV": "production"
diff --git a/mcp-diagrams/GoHighLevel-MCP b/mcp-diagrams/GoHighLevel-MCP
index 69db02d..19b03fe 160000
--- a/mcp-diagrams/GoHighLevel-MCP
+++ b/mcp-diagrams/GoHighLevel-MCP
@@ -1 +1 @@
-Subproject commit 69db02d7cf9aca4fc39ae34b6f20f26617e57316
+Subproject commit 19b03fe777b18657c9e31ec79e740fac568cfa03
diff --git a/mcp-diagrams/ghl-mcp-apps-only/.env.example b/mcp-diagrams/ghl-mcp-apps-only/.env.example
new file mode 100644
index 0000000..08cb1a8
--- /dev/null
+++ b/mcp-diagrams/ghl-mcp-apps-only/.env.example
@@ -0,0 +1,11 @@
+# GoHighLevel API Configuration
+GHL_API_KEY=your_private_integration_api_key_here
+GHL_BASE_URL=https://services.leadconnectorhq.com
+GHL_LOCATION_ID=your_location_id_here
+
+# Server Configuration
+MCP_SERVER_PORT=8000
+NODE_ENV=development
+
+# Optional: For AI features
+OPENAI_API_KEY=your_openai_key_here_optional
diff --git a/mcp-diagrams/ghl-mcp-apps-only/dist/app-ui/agent-stats.html b/mcp-diagrams/ghl-mcp-apps-only/dist/app-ui/agent-stats.html
new file mode 100644
index 0000000..ffbf138
--- /dev/null
+++ b/mcp-diagrams/ghl-mcp-apps-only/dist/app-ui/agent-stats.html
@@ -0,0 +1,481 @@
+
+
+
+
+
+ GHL Agent Leaderboard
+
+
+
+
+
+
+
+ Loading agent leaderboard...
+
+
+
+
diff --git a/mcp-diagrams/ghl-mcp-apps-only/dist/app-ui/calendar-view.html b/mcp-diagrams/ghl-mcp-apps-only/dist/app-ui/calendar-view.html
new file mode 100644
index 0000000..13d2c93
--- /dev/null
+++ b/mcp-diagrams/ghl-mcp-apps-only/dist/app-ui/calendar-view.html
@@ -0,0 +1,544 @@
+
+
+
+
+
+ GHL Calendar View
+
+
+
+
+
+
+
diff --git a/mcp-diagrams/ghl-mcp-apps-only/dist/app-ui/campaign-stats.html b/mcp-diagrams/ghl-mcp-apps-only/dist/app-ui/campaign-stats.html
new file mode 100644
index 0000000..b3ddb43
--- /dev/null
+++ b/mcp-diagrams/ghl-mcp-apps-only/dist/app-ui/campaign-stats.html
@@ -0,0 +1,442 @@
+
+
+
+
+
+ Campaign Performance Dashboard
+
+
+
+
+
+
Loading campaign performance data...
+
+
+
diff --git a/mcp-diagrams/ghl-mcp-apps-only/dist/app-ui/contact-grid.html b/mcp-diagrams/ghl-mcp-apps-only/dist/app-ui/contact-grid.html
new file mode 100644
index 0000000..c1cbee7
--- /dev/null
+++ b/mcp-diagrams/ghl-mcp-apps-only/dist/app-ui/contact-grid.html
@@ -0,0 +1,501 @@
+
+
+
+
+
+ GHL Contact Grid
+
+
+
+
+
+
+
diff --git a/mcp-diagrams/ghl-mcp-apps-only/dist/app-ui/contact-timeline.html b/mcp-diagrams/ghl-mcp-apps-only/dist/app-ui/contact-timeline.html
new file mode 100644
index 0000000..dbfba44
--- /dev/null
+++ b/mcp-diagrams/ghl-mcp-apps-only/dist/app-ui/contact-timeline.html
@@ -0,0 +1,355 @@
+
+
+
+
+
+ Contact Activity Timeline
+
+
+
+
+
+
+
diff --git a/mcp-diagrams/ghl-mcp-apps-only/dist/app-ui/invoice-preview.html b/mcp-diagrams/ghl-mcp-apps-only/dist/app-ui/invoice-preview.html
new file mode 100644
index 0000000..1b024ca
--- /dev/null
+++ b/mcp-diagrams/ghl-mcp-apps-only/dist/app-ui/invoice-preview.html
@@ -0,0 +1,474 @@
+
+
+
+
+
+ Invoice Preview
+
+
+
+
+
+
+
diff --git a/mcp-diagrams/ghl-mcp-apps-only/dist/app-ui/mcp-app.html b/mcp-diagrams/ghl-mcp-apps-only/dist/app-ui/mcp-app.html
new file mode 100644
index 0000000..72e960d
--- /dev/null
+++ b/mcp-diagrams/ghl-mcp-apps-only/dist/app-ui/mcp-app.html
@@ -0,0 +1,168 @@
+
+
+
+
+
+ GHL Conversation
+
+
+
+
+
+
Loading conversation...
+
+
+
diff --git a/mcp-diagrams/ghl-mcp-apps-only/dist/app-ui/opportunity-card.html b/mcp-diagrams/ghl-mcp-apps-only/dist/app-ui/opportunity-card.html
new file mode 100644
index 0000000..f3c7161
--- /dev/null
+++ b/mcp-diagrams/ghl-mcp-apps-only/dist/app-ui/opportunity-card.html
@@ -0,0 +1,520 @@
+
+
+
+
+
+ GHL Opportunity Card
+
+
+
+
+
+
Loading opportunity...
+
+
+
diff --git a/mcp-diagrams/ghl-mcp-apps-only/dist/app-ui/pipeline-board.html b/mcp-diagrams/ghl-mcp-apps-only/dist/app-ui/pipeline-board.html
new file mode 100644
index 0000000..88cbec5
--- /dev/null
+++ b/mcp-diagrams/ghl-mcp-apps-only/dist/app-ui/pipeline-board.html
@@ -0,0 +1,533 @@
+
+
+
+
+
+ GHL Pipeline Board
+
+
+
+
+
+
Loading pipeline board...
+
+
+
+
+
+
+
+
+
+
diff --git a/mcp-diagrams/ghl-mcp-apps-only/dist/app-ui/quick-book.html b/mcp-diagrams/ghl-mcp-apps-only/dist/app-ui/quick-book.html
new file mode 100644
index 0000000..b25ad6a
--- /dev/null
+++ b/mcp-diagrams/ghl-mcp-apps-only/dist/app-ui/quick-book.html
@@ -0,0 +1,453 @@
+
+
+
+
+
+ GHL Quick Booking
+
+
+
+
+
+
Loading available slots...
+
+
+
diff --git a/mcp-diagrams/ghl-mcp-apps-only/dist/app-ui/workflow-status.html b/mcp-diagrams/ghl-mcp-apps-only/dist/app-ui/workflow-status.html
new file mode 100644
index 0000000..6e4be47
--- /dev/null
+++ b/mcp-diagrams/ghl-mcp-apps-only/dist/app-ui/workflow-status.html
@@ -0,0 +1,352 @@
+
+
+
+
+
+ GHL Workflow Status Monitor
+
+
+
+
+
+
+
+ Loading workflow status...
+
+
+
+
diff --git a/mcp-diagrams/ghl-mcp-apps-only/dist/apps/index.js b/mcp-diagrams/ghl-mcp-apps-only/dist/apps/index.js
new file mode 100644
index 0000000..9117063
--- /dev/null
+++ b/mcp-diagrams/ghl-mcp-apps-only/dist/apps/index.js
@@ -0,0 +1,654 @@
+/**
+ * MCP Apps Manager
+ * Manages rich UI components for GoHighLevel MCP Server
+ */
+import * as fs from 'fs';
+import * as path from 'path';
+import { fileURLToPath } from 'url';
+/**
+ * MCP Apps Manager class
+ * Registers app tools and handles structuredContent responses
+ */
+// ESM equivalent of __dirname
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = path.dirname(__filename);
+// Resolve UI build path - works regardless of working directory
+function getUIBuildPath() {
+ // When compiled, this file is at dist/apps/index.js
+ // UI files are at dist/app-ui/
+ const fromDist = path.resolve(__dirname, '..', 'app-ui');
+ if (fs.existsSync(fromDist)) {
+ return fromDist;
+ }
+ // Fallback: try process.cwd() based paths
+ const appUiPath = path.join(process.cwd(), 'dist', 'app-ui');
+ if (fs.existsSync(appUiPath)) {
+ return appUiPath;
+ }
+ // Default fallback
+ return fromDist;
+}
+export class MCPAppsManager {
+ ghlClient;
+ resourceHandlers = new Map();
+ uiBuildPath;
+ constructor(ghlClient) {
+ this.ghlClient = ghlClient;
+ this.uiBuildPath = getUIBuildPath();
+ process.stderr.write(`[MCP Apps] UI build path: ${this.uiBuildPath}\n`);
+ this.registerResourceHandlers();
+ }
+ /**
+ * Register all UI resource handlers
+ */
+ registerResourceHandlers() {
+ const resources = [
+ // All 11 MCP Apps
+ { uri: 'ui://ghl/mcp-app', file: 'mcp-app.html' },
+ { uri: 'ui://ghl/pipeline-board', file: 'pipeline-board.html' },
+ { uri: 'ui://ghl/quick-book', file: 'quick-book.html' },
+ { uri: 'ui://ghl/opportunity-card', file: 'opportunity-card.html' },
+ { uri: 'ui://ghl/contact-grid', file: 'contact-grid.html' },
+ { uri: 'ui://ghl/calendar-view', file: 'calendar-view.html' },
+ { uri: 'ui://ghl/invoice-preview', file: 'invoice-preview.html' },
+ { uri: 'ui://ghl/campaign-stats', file: 'campaign-stats.html' },
+ { uri: 'ui://ghl/agent-stats', file: 'agent-stats.html' },
+ { uri: 'ui://ghl/contact-timeline', file: 'contact-timeline.html' },
+ { uri: 'ui://ghl/workflow-status', file: 'workflow-status.html' },
+ ];
+ for (const resource of resources) {
+ this.resourceHandlers.set(resource.uri, {
+ uri: resource.uri,
+ mimeType: 'text/html;profile=mcp-app',
+ getContent: () => this.loadUIResource(resource.file),
+ });
+ }
+ }
+ /**
+ * Load UI resource from build directory
+ */
+ loadUIResource(filename) {
+ const filePath = path.join(this.uiBuildPath, filename);
+ try {
+ return fs.readFileSync(filePath, 'utf-8');
+ }
+ catch (error) {
+ process.stderr.write(`[MCP Apps] UI resource not found: ${filePath}\n`);
+ return this.getFallbackHTML(filename);
+ }
+ }
+ /**
+ * Generate fallback HTML when UI resource is not built
+ */
+ getFallbackHTML(filename) {
+ const componentName = filename.replace('.html', '');
+ return `
+
+
+
+
+
+ GHL ${componentName}
+
+
+
+
+
UI component "${componentName}" is loading...
+
Run npm run build:ui to build UI components.
+
+
+
+
+ `.trim();
+ }
+ /**
+ * Get tool definitions for all app tools
+ */
+ getToolDefinitions() {
+ return [
+ // 1. Contact Grid - search and display contacts
+ {
+ name: 'view_contact_grid',
+ description: 'Display contact search results in a data grid with sorting and pagination. Returns a visual UI component.',
+ inputSchema: {
+ type: 'object',
+ properties: {
+ query: { type: 'string', description: 'Search query string' },
+ limit: { type: 'number', description: 'Maximum results (default: 25)' }
+ }
+ },
+ _meta: {
+ ui: { resourceUri: 'ui://ghl/contact-grid' }
+ }
+ },
+ // 2. Pipeline Board - Kanban view of opportunities
+ {
+ name: 'view_pipeline_board',
+ description: 'Display a pipeline as an interactive Kanban board with opportunities. Returns a visual UI component.',
+ inputSchema: {
+ type: 'object',
+ properties: {
+ pipelineId: { type: 'string', description: 'Pipeline ID to display' }
+ },
+ required: ['pipelineId']
+ },
+ _meta: {
+ ui: { resourceUri: 'ui://ghl/pipeline-board' }
+ }
+ },
+ // 3. Quick Book - appointment booking
+ {
+ name: 'view_quick_book',
+ description: 'Display a quick booking interface for scheduling appointments. Returns a visual UI component.',
+ inputSchema: {
+ type: 'object',
+ properties: {
+ calendarId: { type: 'string', description: 'Calendar ID for booking' },
+ contactId: { type: 'string', description: 'Optional contact ID to pre-fill' }
+ },
+ required: ['calendarId']
+ },
+ _meta: {
+ ui: { resourceUri: 'ui://ghl/quick-book' }
+ }
+ },
+ // 4. Opportunity Card - single opportunity details
+ {
+ name: 'view_opportunity_card',
+ description: 'Display a single opportunity with details, value, and stage info. Returns a visual UI component.',
+ inputSchema: {
+ type: 'object',
+ properties: {
+ opportunityId: { type: 'string', description: 'Opportunity ID to display' }
+ },
+ required: ['opportunityId']
+ },
+ _meta: {
+ ui: { resourceUri: 'ui://ghl/opportunity-card' }
+ }
+ },
+ // 5. Calendar View - calendar with events
+ {
+ name: 'view_calendar',
+ description: 'Display a calendar with events and appointments. Returns a visual UI component.',
+ inputSchema: {
+ type: 'object',
+ properties: {
+ calendarId: { type: 'string', description: 'Calendar ID to display' },
+ startDate: { type: 'string', description: 'Start date (ISO format)' },
+ endDate: { type: 'string', description: 'End date (ISO format)' }
+ },
+ required: ['calendarId']
+ },
+ _meta: {
+ ui: { resourceUri: 'ui://ghl/calendar-view' }
+ }
+ },
+ // 6. Invoice Preview - invoice details
+ {
+ name: 'view_invoice',
+ description: 'Display an invoice preview with line items and payment status. Returns a visual UI component.',
+ inputSchema: {
+ type: 'object',
+ properties: {
+ invoiceId: { type: 'string', description: 'Invoice ID to display' }
+ },
+ required: ['invoiceId']
+ },
+ _meta: {
+ ui: { resourceUri: 'ui://ghl/invoice-preview' }
+ }
+ },
+ // 7. Campaign Stats - campaign performance metrics
+ {
+ name: 'view_campaign_stats',
+ description: 'Display campaign statistics and performance metrics. Returns a visual UI component.',
+ inputSchema: {
+ type: 'object',
+ properties: {
+ campaignId: { type: 'string', description: 'Campaign ID to display stats for' }
+ },
+ required: ['campaignId']
+ },
+ _meta: {
+ ui: { resourceUri: 'ui://ghl/campaign-stats' }
+ }
+ },
+ // 8. Agent Stats - agent/user performance
+ {
+ name: 'view_agent_stats',
+ description: 'Display agent/user performance statistics and metrics. Returns a visual UI component.',
+ inputSchema: {
+ type: 'object',
+ properties: {
+ userId: { type: 'string', description: 'User/Agent ID to display stats for' },
+ dateRange: { type: 'string', description: 'Date range (e.g., "last7days", "last30days")' }
+ }
+ },
+ _meta: {
+ ui: { resourceUri: 'ui://ghl/agent-stats' }
+ }
+ },
+ // 9. Contact Timeline - activity history for a contact
+ {
+ name: 'view_contact_timeline',
+ description: 'Display a contact\'s activity timeline with all interactions. Returns a visual UI component.',
+ inputSchema: {
+ type: 'object',
+ properties: {
+ contactId: { type: 'string', description: 'Contact ID to display timeline for' }
+ },
+ required: ['contactId']
+ },
+ _meta: {
+ ui: { resourceUri: 'ui://ghl/contact-timeline' }
+ }
+ },
+ // 10. Workflow Status - workflow execution status
+ {
+ name: 'view_workflow_status',
+ description: 'Display workflow execution status and history. Returns a visual UI component.',
+ inputSchema: {
+ type: 'object',
+ properties: {
+ workflowId: { type: 'string', description: 'Workflow ID to display status for' }
+ },
+ required: ['workflowId']
+ },
+ _meta: {
+ ui: { resourceUri: 'ui://ghl/workflow-status' }
+ }
+ },
+ // 11. MCP App - generic/main dashboard
+ {
+ name: 'view_dashboard',
+ description: 'Display the main GHL dashboard overview. Returns a visual UI component.',
+ inputSchema: {
+ type: 'object',
+ properties: {}
+ },
+ _meta: {
+ ui: { resourceUri: 'ui://ghl/mcp-app' }
+ }
+ },
+ // 12. Update Opportunity - action tool for UI to update opportunities
+ {
+ name: 'update_opportunity',
+ description: 'Update an opportunity (move to stage, change value, status, etc.)',
+ inputSchema: {
+ type: 'object',
+ properties: {
+ opportunityId: { type: 'string', description: 'Opportunity ID to update' },
+ pipelineStageId: { type: 'string', description: 'New stage ID (for moving)' },
+ name: { type: 'string', description: 'Opportunity name' },
+ monetaryValue: { type: 'number', description: 'Monetary value' },
+ status: { type: 'string', enum: ['open', 'won', 'lost', 'abandoned'], description: 'Opportunity status' }
+ },
+ required: ['opportunityId']
+ }
+ }
+ ];
+ }
+ /**
+ * Get app tool names for routing
+ */
+ getAppToolNames() {
+ return [
+ 'view_contact_grid',
+ 'view_pipeline_board',
+ 'view_quick_book',
+ 'view_opportunity_card',
+ 'view_calendar',
+ 'view_invoice',
+ 'view_campaign_stats',
+ 'view_agent_stats',
+ 'view_contact_timeline',
+ 'view_workflow_status',
+ 'view_dashboard',
+ 'update_opportunity'
+ ];
+ }
+ /**
+ * Check if a tool is an app tool
+ */
+ isAppTool(toolName) {
+ return this.getAppToolNames().includes(toolName);
+ }
+ /**
+ * Execute an app tool
+ */
+ async executeTool(toolName, args) {
+ process.stderr.write(`[MCP Apps] Executing app tool: ${toolName}\n`);
+ switch (toolName) {
+ case 'view_contact_grid':
+ return await this.viewContactGrid(args.query, args.limit);
+ case 'view_pipeline_board':
+ return await this.viewPipelineBoard(args.pipelineId);
+ case 'view_quick_book':
+ return await this.viewQuickBook(args.calendarId, args.contactId);
+ case 'view_opportunity_card':
+ return await this.viewOpportunityCard(args.opportunityId);
+ case 'view_calendar':
+ return await this.viewCalendar(args.calendarId, args.startDate, args.endDate);
+ case 'view_invoice':
+ return await this.viewInvoice(args.invoiceId);
+ case 'view_campaign_stats':
+ return await this.viewCampaignStats(args.campaignId);
+ case 'view_agent_stats':
+ return await this.viewAgentStats(args.userId, args.dateRange);
+ case 'view_contact_timeline':
+ return await this.viewContactTimeline(args.contactId);
+ case 'view_workflow_status':
+ return await this.viewWorkflowStatus(args.workflowId);
+ case 'view_dashboard':
+ return await this.viewDashboard();
+ case 'update_opportunity':
+ return await this.updateOpportunity(args);
+ default:
+ throw new Error(`Unknown app tool: ${toolName}`);
+ }
+ }
+ /**
+ * View contact grid (search results)
+ */
+ async viewContactGrid(query, limit) {
+ const response = await this.ghlClient.searchContacts({
+ locationId: this.ghlClient.getConfig().locationId,
+ query: query,
+ limit: limit || 25
+ });
+ if (!response.success) {
+ throw new Error(response.error?.message || 'Failed to search contacts');
+ }
+ const data = response.data;
+ const resourceHandler = this.resourceHandlers.get('ui://ghl/contact-grid');
+ return this.createAppResult(`Found ${data?.contacts?.length || 0} contacts`, resourceHandler.uri, resourceHandler.mimeType, resourceHandler.getContent(), data);
+ }
+ /**
+ * View pipeline board (Kanban)
+ */
+ async viewPipelineBoard(pipelineId) {
+ const [pipelinesResponse, opportunitiesResponse] = await Promise.all([
+ this.ghlClient.getPipelines(),
+ this.ghlClient.searchOpportunities({
+ location_id: this.ghlClient.getConfig().locationId,
+ pipeline_id: pipelineId
+ })
+ ]);
+ if (!pipelinesResponse.success) {
+ throw new Error(pipelinesResponse.error?.message || 'Failed to get pipeline');
+ }
+ const pipeline = pipelinesResponse.data?.pipelines?.find((p) => p.id === pipelineId);
+ const opportunities = opportunitiesResponse.data?.opportunities || [];
+ // Simplify opportunity data to only include fields the UI needs (reduces payload size)
+ const simplifiedOpportunities = opportunities.map((opp) => ({
+ id: opp.id,
+ name: opp.name || 'Untitled',
+ pipelineStageId: opp.pipelineStageId,
+ status: opp.status || 'open',
+ monetaryValue: opp.monetaryValue || 0,
+ contact: opp.contact ? {
+ name: opp.contact.name || 'Unknown',
+ email: opp.contact.email,
+ phone: opp.contact.phone
+ } : { name: 'Unknown' },
+ updatedAt: opp.updatedAt || opp.createdAt,
+ createdAt: opp.createdAt,
+ source: opp.source
+ }));
+ const data = {
+ pipeline,
+ opportunities: simplifiedOpportunities,
+ stages: pipeline?.stages || []
+ };
+ const resourceHandler = this.resourceHandlers.get('ui://ghl/pipeline-board');
+ return this.createAppResult(`Pipeline: ${pipeline?.name || 'Unknown'} (${opportunities.length} opportunities)`, resourceHandler.uri, resourceHandler.mimeType, resourceHandler.getContent(), data);
+ }
+ /**
+ * View quick book interface
+ */
+ async viewQuickBook(calendarId, contactId) {
+ const [calendarResponse, contactResponse] = await Promise.all([
+ this.ghlClient.getCalendar(calendarId),
+ contactId ? this.ghlClient.getContact(contactId) : Promise.resolve({ success: true, data: null })
+ ]);
+ if (!calendarResponse.success) {
+ throw new Error(calendarResponse.error?.message || 'Failed to get calendar');
+ }
+ const data = {
+ calendar: calendarResponse.data,
+ contact: contactResponse.data,
+ locationId: this.ghlClient.getConfig().locationId
+ };
+ const resourceHandler = this.resourceHandlers.get('ui://ghl/quick-book');
+ return this.createAppResult(`Quick booking for calendar: ${calendarResponse.data?.name || calendarId}`, resourceHandler.uri, resourceHandler.mimeType, resourceHandler.getContent(), data);
+ }
+ /**
+ * View opportunity card
+ */
+ async viewOpportunityCard(opportunityId) {
+ const response = await this.ghlClient.getOpportunity(opportunityId);
+ if (!response.success) {
+ throw new Error(response.error?.message || 'Failed to get opportunity');
+ }
+ const opportunity = response.data;
+ const resourceHandler = this.resourceHandlers.get('ui://ghl/opportunity-card');
+ return this.createAppResult(`Opportunity: ${opportunity?.name || opportunityId}`, resourceHandler.uri, resourceHandler.mimeType, resourceHandler.getContent(), opportunity);
+ }
+ /**
+ * View calendar
+ */
+ async viewCalendar(calendarId, startDate, endDate) {
+ const now = new Date();
+ const start = startDate || new Date(now.getFullYear(), now.getMonth(), 1).toISOString();
+ const end = endDate || new Date(now.getFullYear(), now.getMonth() + 1, 0).toISOString();
+ const [calendarResponse, eventsResponse] = await Promise.all([
+ this.ghlClient.getCalendar(calendarId),
+ this.ghlClient.getCalendarEvents({
+ calendarId: calendarId,
+ startTime: start,
+ endTime: end,
+ locationId: this.ghlClient.getConfig().locationId
+ })
+ ]);
+ if (!calendarResponse.success) {
+ throw new Error(calendarResponse.error?.message || 'Failed to get calendar');
+ }
+ const calendar = calendarResponse.data;
+ const data = {
+ calendar: calendarResponse.data,
+ events: eventsResponse.data?.events || [],
+ startDate: start,
+ endDate: end
+ };
+ const resourceHandler = this.resourceHandlers.get('ui://ghl/calendar-view');
+ return this.createAppResult(`Calendar: ${calendar?.name || 'Unknown'} (${data.events.length} events)`, resourceHandler.uri, resourceHandler.mimeType, resourceHandler.getContent(), data);
+ }
+ /**
+ * View campaign stats
+ */
+ async viewCampaignStats(campaignId) {
+ // Get email campaigns
+ const response = await this.ghlClient.getEmailCampaigns({});
+ const campaigns = response.data?.schedules || [];
+ const campaign = campaigns.find((c) => c.id === campaignId) || { id: campaignId };
+ const data = {
+ campaign,
+ campaigns,
+ campaignId,
+ locationId: this.ghlClient.getConfig().locationId
+ };
+ const resourceHandler = this.resourceHandlers.get('ui://ghl/campaign-stats');
+ return this.createAppResult(`Campaign stats: ${campaign?.name || campaignId}`, resourceHandler.uri, resourceHandler.mimeType, resourceHandler.getContent(), data);
+ }
+ /**
+ * View agent stats
+ */
+ async viewAgentStats(userId, dateRange) {
+ // Get location info which may include user data
+ const locationResponse = await this.ghlClient.getLocationById(this.ghlClient.getConfig().locationId);
+ const data = {
+ userId,
+ dateRange: dateRange || 'last30days',
+ location: locationResponse.data,
+ locationId: this.ghlClient.getConfig().locationId
+ };
+ const resourceHandler = this.resourceHandlers.get('ui://ghl/agent-stats');
+ return this.createAppResult(userId ? `Agent stats: ${userId}` : 'Agent overview', resourceHandler.uri, resourceHandler.mimeType, resourceHandler.getContent(), data);
+ }
+ /**
+ * View contact timeline
+ */
+ async viewContactTimeline(contactId) {
+ const [contactResponse, notesResponse, tasksResponse] = await Promise.all([
+ this.ghlClient.getContact(contactId),
+ this.ghlClient.getContactNotes(contactId),
+ this.ghlClient.getContactTasks(contactId)
+ ]);
+ if (!contactResponse.success) {
+ throw new Error(contactResponse.error?.message || 'Failed to get contact');
+ }
+ const contact = contactResponse.data;
+ const data = {
+ contact: contactResponse.data,
+ notes: notesResponse.data || [],
+ tasks: tasksResponse.data || []
+ };
+ const resourceHandler = this.resourceHandlers.get('ui://ghl/contact-timeline');
+ return this.createAppResult(`Timeline for ${contact?.firstName || ''} ${contact?.lastName || ''}`, resourceHandler.uri, resourceHandler.mimeType, resourceHandler.getContent(), data);
+ }
+ /**
+ * View workflow status
+ */
+ async viewWorkflowStatus(workflowId) {
+ const response = await this.ghlClient.getWorkflows({
+ locationId: this.ghlClient.getConfig().locationId
+ });
+ const workflows = response.data?.workflows || [];
+ const workflow = workflows.find((w) => w.id === workflowId) || { id: workflowId };
+ const data = {
+ workflow,
+ workflows,
+ workflowId,
+ locationId: this.ghlClient.getConfig().locationId
+ };
+ const resourceHandler = this.resourceHandlers.get('ui://ghl/workflow-status');
+ return this.createAppResult(`Workflow: ${workflow?.name || workflowId}`, resourceHandler.uri, resourceHandler.mimeType, resourceHandler.getContent(), data);
+ }
+ /**
+ * View main dashboard
+ */
+ async viewDashboard() {
+ const [contactsResponse, pipelinesResponse, calendarsResponse] = await Promise.all([
+ this.ghlClient.searchContacts({ locationId: this.ghlClient.getConfig().locationId, limit: 10 }),
+ this.ghlClient.getPipelines(),
+ this.ghlClient.getCalendars()
+ ]);
+ const data = {
+ recentContacts: contactsResponse.data?.contacts || [],
+ pipelines: pipelinesResponse.data?.pipelines || [],
+ calendars: calendarsResponse.data?.calendars || [],
+ locationId: this.ghlClient.getConfig().locationId
+ };
+ const resourceHandler = this.resourceHandlers.get('ui://ghl/mcp-app');
+ return this.createAppResult('GHL Dashboard Overview', resourceHandler.uri, resourceHandler.mimeType, resourceHandler.getContent(), data);
+ }
+ /**
+ * View invoice
+ */
+ async viewInvoice(invoiceId) {
+ const response = await this.ghlClient.getInvoice(invoiceId, {
+ altId: this.ghlClient.getConfig().locationId,
+ altType: 'location'
+ });
+ if (!response.success) {
+ throw new Error(response.error?.message || 'Failed to get invoice');
+ }
+ const invoice = response.data;
+ const resourceHandler = this.resourceHandlers.get('ui://ghl/invoice-preview');
+ return this.createAppResult(`Invoice #${invoice?.invoiceNumber || invoiceId} - ${invoice?.status || 'Unknown status'}`, resourceHandler.uri, resourceHandler.mimeType, resourceHandler.getContent(), invoice);
+ }
+ /**
+ * Update opportunity (action tool for UI)
+ */
+ async updateOpportunity(args) {
+ const { opportunityId, ...updates } = args;
+ // Build the update payload
+ const updatePayload = {};
+ if (updates.pipelineStageId)
+ updatePayload.pipelineStageId = updates.pipelineStageId;
+ if (updates.name)
+ updatePayload.name = updates.name;
+ if (updates.monetaryValue !== undefined)
+ updatePayload.monetaryValue = updates.monetaryValue;
+ if (updates.status)
+ updatePayload.status = updates.status;
+ process.stderr.write(`[MCP Apps] Updating opportunity ${opportunityId}: ${JSON.stringify(updatePayload)}\n`);
+ const response = await this.ghlClient.updateOpportunity(opportunityId, updatePayload);
+ if (!response.success) {
+ throw new Error(response.error?.message || 'Failed to update opportunity');
+ }
+ const opportunity = response.data;
+ return {
+ content: [{ type: 'text', text: `Updated opportunity: ${opportunity?.name || opportunityId}` }],
+ structuredContent: {
+ success: true,
+ opportunity: {
+ id: opportunity?.id,
+ name: opportunity?.name,
+ pipelineStageId: opportunity?.pipelineStageId,
+ monetaryValue: opportunity?.monetaryValue,
+ status: opportunity?.status
+ }
+ }
+ };
+ }
+ /**
+ * Create app tool result with structuredContent
+ */
+ createAppResult(textSummary, resourceUri, mimeType, htmlContent, data) {
+ // structuredContent is the data object that gets passed to ontoolresult
+ // The UI accesses it via result.structuredContent
+ return {
+ content: [{ type: 'text', text: textSummary }],
+ structuredContent: data
+ };
+ }
+ /**
+ * Inject data into HTML as a script tag
+ */
+ injectDataIntoHTML(html, data) {
+ const dataScript = ``;
+ // Insert before or at the beginning of
+ if (html.includes('')) {
+ return html.replace('', `${dataScript}`);
+ }
+ else if (html.includes('')) {
+ return html.replace('', `${dataScript}`);
+ }
+ else {
+ return dataScript + html;
+ }
+ }
+ /**
+ * Get resource handler by URI
+ */
+ getResourceHandler(uri) {
+ return this.resourceHandlers.get(uri);
+ }
+ /**
+ * Get all registered resource URIs
+ */
+ getResourceURIs() {
+ return Array.from(this.resourceHandlers.keys());
+ }
+}
diff --git a/mcp-diagrams/ghl-mcp-apps-only/dist/clients/ghl-api-client.js b/mcp-diagrams/ghl-mcp-apps-only/dist/clients/ghl-api-client.js
new file mode 100644
index 0000000..4e5b0de
--- /dev/null
+++ b/mcp-diagrams/ghl-mcp-apps-only/dist/clients/ghl-api-client.js
@@ -0,0 +1,5107 @@
+/**
+ * GoHighLevel API Client
+ * Implements exact API endpoints from OpenAPI specifications v2021-07-28 (Contacts) and v2021-04-15 (Conversations)
+ */
+import axios from 'axios';
+/**
+ * GoHighLevel API Client
+ * Handles all API communication with GHL services
+ */
+export class GHLApiClient {
+ axiosInstance;
+ config;
+ constructor(config) {
+ this.config = config;
+ // Create axios instance with base configuration
+ this.axiosInstance = axios.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 && typeof searchParams.filters.email === 'string' && searchParams.filters.email.trim()) {
+ filters.email = searchParams.filters.email.trim();
+ hasFilters = true;
+ }
+ if (searchParams.filters.phone && typeof searchParams.filters.phone === 'string' && 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 };
+ }
+ /**
+ * Generic request method for new endpoints
+ * Used by new tool modules that don't have specific client methods yet
+ */
+ async makeRequest(method, path, body) {
+ try {
+ let response;
+ switch (method) {
+ case 'GET':
+ response = await this.axiosInstance.get(path);
+ break;
+ case 'POST':
+ response = await this.axiosInstance.post(path, body);
+ break;
+ case 'PUT':
+ response = await this.axiosInstance.put(path, body);
+ break;
+ case 'PATCH':
+ response = await this.axiosInstance.patch(path, body);
+ break;
+ case 'DELETE':
+ response = await this.axiosInstance.delete(path);
+ break;
+ }
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * 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(`/locations/${locationId}`);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ /**
+ * Create a new location/sub-account
+ * POST /locations/
+ */
+ async createLocation(locationData) {
+ try {
+ const response = await this.axiosInstance.post('/locations/', locationData);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ /**
+ * Update location/sub-account
+ * PUT /locations/{locationId}
+ */
+ async updateLocation(locationId, updates) {
+ try {
+ const response = await this.axiosInstance.put(`/locations/${locationId}`, updates);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ /**
+ * Delete location/sub-account
+ * DELETE /locations/{locationId}
+ */
+ async deleteLocation(locationId, deleteTwilioAccount) {
+ try {
+ const response = await this.axiosInstance.delete(`/locations/${locationId}`, {
+ params: { deleteTwilioAccount }
+ });
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ /**
+ * LOCATION TAGS API METHODS
+ */
+ /**
+ * Get location tags
+ * GET /locations/{locationId}/tags
+ */
+ async getLocationTags(locationId) {
+ try {
+ const response = await this.axiosInstance.get(`/locations/${locationId}/tags`);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ /**
+ * Create location tag
+ * POST /locations/{locationId}/tags
+ */
+ async createLocationTag(locationId, tagData) {
+ try {
+ const response = await this.axiosInstance.post(`/locations/${locationId}/tags`, tagData);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ /**
+ * Get location tag by ID
+ * GET /locations/{locationId}/tags/{tagId}
+ */
+ async getLocationTag(locationId, tagId) {
+ try {
+ const response = await this.axiosInstance.get(`/locations/${locationId}/tags/${tagId}`);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ /**
+ * Update location tag
+ * PUT /locations/{locationId}/tags/{tagId}
+ */
+ async updateLocationTag(locationId, tagId, tagData) {
+ try {
+ const response = await this.axiosInstance.put(`/locations/${locationId}/tags/${tagId}`, tagData);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ /**
+ * Delete location tag
+ * DELETE /locations/{locationId}/tags/{tagId}
+ */
+ async deleteLocationTag(locationId, tagId) {
+ try {
+ const response = await this.axiosInstance.delete(`/locations/${locationId}/tags/${tagId}`);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ /**
+ * LOCATION TASKS API METHODS
+ */
+ /**
+ * Search location tasks
+ * POST /locations/{locationId}/tasks/search
+ */
+ async searchLocationTasks(locationId, searchParams) {
+ try {
+ const response = await this.axiosInstance.post(`/locations/${locationId}/tasks/search`, searchParams);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ /**
+ * CUSTOM FIELDS API METHODS
+ */
+ /**
+ * Get custom fields for location
+ * GET /locations/{locationId}/customFields
+ */
+ async getLocationCustomFields(locationId, model) {
+ try {
+ const params = {};
+ if (model)
+ params.model = model;
+ const response = await this.axiosInstance.get(`/locations/${locationId}/customFields`, { params });
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ /**
+ * Create custom field for location
+ * POST /locations/{locationId}/customFields
+ */
+ async createLocationCustomField(locationId, fieldData) {
+ try {
+ const response = await this.axiosInstance.post(`/locations/${locationId}/customFields`, fieldData);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ /**
+ * Get custom field by ID
+ * GET /locations/{locationId}/customFields/{id}
+ */
+ async getLocationCustomField(locationId, customFieldId) {
+ try {
+ const response = await this.axiosInstance.get(`/locations/${locationId}/customFields/${customFieldId}`);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ /**
+ * Update custom field
+ * PUT /locations/{locationId}/customFields/{id}
+ */
+ async updateLocationCustomField(locationId, customFieldId, fieldData) {
+ try {
+ const response = await this.axiosInstance.put(`/locations/${locationId}/customFields/${customFieldId}`, fieldData);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ /**
+ * Delete custom field
+ * DELETE /locations/{locationId}/customFields/{id}
+ */
+ async deleteLocationCustomField(locationId, customFieldId) {
+ try {
+ const response = await this.axiosInstance.delete(`/locations/${locationId}/customFields/${customFieldId}`);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ /**
+ * Upload file to custom fields
+ * POST /locations/{locationId}/customFields/upload
+ */
+ async uploadLocationCustomFieldFile(locationId, uploadData) {
+ try {
+ // Note: This endpoint expects multipart/form-data but we'll handle it as JSON for now
+ // In a real implementation, you'd use FormData for file uploads
+ const response = await this.axiosInstance.post(`/locations/${locationId}/customFields/upload`, uploadData, { headers: { 'Content-Type': 'multipart/form-data' } });
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ /**
+ * CUSTOM VALUES API METHODS
+ */
+ /**
+ * Get custom values for location
+ * GET /locations/{locationId}/customValues
+ */
+ async getLocationCustomValues(locationId) {
+ try {
+ const response = await this.axiosInstance.get(`/locations/${locationId}/customValues`);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ /**
+ * Create custom value for location
+ * POST /locations/{locationId}/customValues
+ */
+ async createLocationCustomValue(locationId, valueData) {
+ try {
+ const response = await this.axiosInstance.post(`/locations/${locationId}/customValues`, valueData);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ /**
+ * Get custom value by ID
+ * GET /locations/{locationId}/customValues/{id}
+ */
+ async getLocationCustomValue(locationId, customValueId) {
+ try {
+ const response = await this.axiosInstance.get(`/locations/${locationId}/customValues/${customValueId}`);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ /**
+ * Update custom value
+ * PUT /locations/{locationId}/customValues/{id}
+ */
+ async updateLocationCustomValue(locationId, customValueId, valueData) {
+ try {
+ const response = await this.axiosInstance.put(`/locations/${locationId}/customValues/${customValueId}`, valueData);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ /**
+ * Delete custom value
+ * DELETE /locations/{locationId}/customValues/{id}
+ */
+ async deleteLocationCustomValue(locationId, customValueId) {
+ try {
+ const response = await this.axiosInstance.delete(`/locations/${locationId}/customValues/${customValueId}`);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ /**
+ * TEMPLATES API METHODS
+ */
+ /**
+ * Get location templates (SMS/Email)
+ * GET /locations/{locationId}/templates
+ */
+ async getLocationTemplates(locationId, params) {
+ try {
+ const queryParams = {
+ originId: params.originId,
+ deleted: params.deleted || false,
+ skip: params.skip || 0,
+ limit: params.limit || 25,
+ ...(params.type && { type: params.type })
+ };
+ const response = await this.axiosInstance.get(`/locations/${locationId}/templates`, { params: queryParams });
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ /**
+ * Delete location template
+ * DELETE /locations/{locationId}/templates/{id}
+ */
+ async deleteLocationTemplate(locationId, templateId) {
+ try {
+ const response = await this.axiosInstance.delete(`/locations/${locationId}/templates/${templateId}`);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ /**
+ * TIMEZONES API METHODS
+ */
+ /**
+ * Get available timezones
+ * GET /locations/{locationId}/timezones
+ */
+ async getTimezones(locationId) {
+ try {
+ const endpoint = locationId ? `/locations/${locationId}/timezones` : '/locations/timezones';
+ const response = await this.axiosInstance.get(endpoint);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ /**
+ * EMAIL ISV (VERIFICATION) API METHODS
+ */
+ /**
+ * Verify email address or contact
+ * POST /email/verify
+ */
+ async verifyEmail(locationId, verificationData) {
+ try {
+ const params = {
+ locationId: locationId
+ };
+ const response = await this.axiosInstance.post('/email/verify', verificationData, { params });
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ /**
+ * ADDITIONAL CONVERSATION/MESSAGE API METHODS
+ */
+ /**
+ * Get email message by ID
+ * GET /conversations/messages/email/{id}
+ */
+ async getEmailMessage(emailMessageId) {
+ try {
+ const response = await this.axiosInstance.get(`/conversations/messages/email/${emailMessageId}`);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ /**
+ * Cancel scheduled email message
+ * DELETE /conversations/messages/email/{emailMessageId}/schedule
+ */
+ async cancelScheduledEmail(emailMessageId) {
+ try {
+ const response = await this.axiosInstance.delete(`/conversations/messages/email/${emailMessageId}/schedule`);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ /**
+ * Add inbound message manually
+ * POST /conversations/messages/inbound
+ */
+ async addInboundMessage(messageData) {
+ try {
+ const response = await this.axiosInstance.post('/conversations/messages/inbound', messageData, { headers: this.getConversationHeaders() });
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ /**
+ * Add outbound call manually
+ * POST /conversations/messages/outbound
+ */
+ async addOutboundCall(messageData) {
+ try {
+ const response = await this.axiosInstance.post('/conversations/messages/outbound', messageData, { headers: this.getConversationHeaders() });
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ /**
+ * Cancel scheduled message
+ * DELETE /conversations/messages/{messageId}/schedule
+ */
+ async cancelScheduledMessage(messageId) {
+ try {
+ const response = await this.axiosInstance.delete(`/conversations/messages/${messageId}/schedule`, { headers: this.getConversationHeaders() });
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ /**
+ * Upload file attachments for messages
+ * POST /conversations/messages/upload
+ */
+ async uploadMessageAttachments(uploadData) {
+ try {
+ const response = await this.axiosInstance.post('/conversations/messages/upload', uploadData, {
+ headers: {
+ ...this.getConversationHeaders(),
+ 'Content-Type': 'multipart/form-data'
+ }
+ });
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ /**
+ * Update message status
+ * PUT /conversations/messages/{messageId}/status
+ */
+ async updateMessageStatus(messageId, statusData) {
+ try {
+ const response = await this.axiosInstance.put(`/conversations/messages/${messageId}/status`, statusData, { headers: this.getConversationHeaders() });
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ /**
+ * Get message recording
+ * GET /conversations/messages/{messageId}/locations/{locationId}/recording
+ */
+ async getMessageRecording(messageId, locationId) {
+ try {
+ const locId = locationId || this.config.locationId;
+ const response = await this.axiosInstance.get(`/conversations/messages/${messageId}/locations/${locId}/recording`, {
+ headers: this.getConversationHeaders(),
+ responseType: 'arraybuffer'
+ });
+ const recordingResponse = {
+ audioData: response.data,
+ contentType: response.headers['content-type'] || 'audio/x-wav',
+ contentDisposition: response.headers['content-disposition'] || 'attachment; filename=audio.wav'
+ };
+ return this.wrapResponse(recordingResponse);
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ /**
+ * Get message transcription
+ * GET /conversations/locations/{locationId}/messages/{messageId}/transcription
+ */
+ async getMessageTranscription(messageId, locationId) {
+ try {
+ const locId = locationId || this.config.locationId;
+ const response = await this.axiosInstance.get(`/conversations/locations/${locId}/messages/${messageId}/transcription`, { headers: this.getConversationHeaders() });
+ return this.wrapResponse({ transcriptions: response.data });
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ /**
+ * Download message transcription
+ * GET /conversations/locations/{locationId}/messages/{messageId}/transcription/download
+ */
+ async downloadMessageTranscription(messageId, locationId) {
+ try {
+ const locId = locationId || this.config.locationId;
+ const response = await this.axiosInstance.get(`/conversations/locations/${locId}/messages/${messageId}/transcription/download`, {
+ headers: this.getConversationHeaders(),
+ responseType: 'text'
+ });
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ /**
+ * Live chat typing indicator
+ * POST /conversations/providers/live-chat/typing
+ */
+ async liveChatTyping(typingData) {
+ try {
+ const response = await this.axiosInstance.post('/conversations/providers/live-chat/typing', typingData, { headers: this.getConversationHeaders() });
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ // ============================================================================
+ // SOCIAL MEDIA POSTING API METHODS
+ // ============================================================================
+ // ===== POST MANAGEMENT =====
+ /**
+ * Search/List Social Media Posts
+ */
+ async searchSocialPosts(searchData) {
+ try {
+ const locationId = this.config.locationId;
+ const response = await this.axiosInstance.post(`/social-media-posting/${locationId}/posts/list`, searchData);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Create Social Media Post
+ */
+ async createSocialPost(postData) {
+ try {
+ const locationId = this.config.locationId;
+ const response = await this.axiosInstance.post(`/social-media-posting/${locationId}/posts`, postData);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Get Social Media Post by ID
+ */
+ async getSocialPost(postId) {
+ try {
+ const locationId = this.config.locationId;
+ const response = await this.axiosInstance.get(`/social-media-posting/${locationId}/posts/${postId}`);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Update Social Media Post
+ */
+ async updateSocialPost(postId, updateData) {
+ try {
+ const locationId = this.config.locationId;
+ const response = await this.axiosInstance.put(`/social-media-posting/${locationId}/posts/${postId}`, updateData);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Delete Social Media Post
+ */
+ async deleteSocialPost(postId) {
+ try {
+ const locationId = this.config.locationId;
+ const response = await this.axiosInstance.delete(`/social-media-posting/${locationId}/posts/${postId}`);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Bulk Delete Social Media Posts
+ */
+ async bulkDeleteSocialPosts(deleteData) {
+ try {
+ const locationId = this.config.locationId;
+ const response = await this.axiosInstance.post(`/social-media-posting/${locationId}/posts/bulk-delete`, deleteData);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ // ===== ACCOUNT MANAGEMENT =====
+ /**
+ * Get Social Media Accounts and Groups
+ */
+ async getSocialAccounts() {
+ try {
+ const locationId = this.config.locationId;
+ const response = await this.axiosInstance.get(`/social-media-posting/${locationId}/accounts`);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Delete Social Media Account
+ */
+ async deleteSocialAccount(accountId, companyId, userId) {
+ try {
+ const locationId = this.config.locationId;
+ const params = {};
+ if (companyId)
+ params.companyId = companyId;
+ if (userId)
+ params.userId = userId;
+ const response = await this.axiosInstance.delete(`/social-media-posting/${locationId}/accounts/${accountId}`, { params });
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ // ===== CSV OPERATIONS =====
+ /**
+ * Upload CSV for Social Media Posts
+ */
+ async uploadSocialCSV(csvData) {
+ try {
+ const locationId = this.config.locationId;
+ // Note: This would typically use FormData for file upload
+ const response = await this.axiosInstance.post(`/social-media-posting/${locationId}/csv`, csvData);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Get CSV Upload Status
+ */
+ async getSocialCSVUploadStatus(skip, limit, includeUsers, userId) {
+ try {
+ const locationId = this.config.locationId;
+ const params = {};
+ if (skip !== undefined)
+ params.skip = skip.toString();
+ if (limit !== undefined)
+ params.limit = limit.toString();
+ if (includeUsers !== undefined)
+ params.includeUsers = includeUsers.toString();
+ if (userId)
+ params.userId = userId;
+ const response = await this.axiosInstance.get(`/social-media-posting/${locationId}/csv`, { params });
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Set Accounts for CSV Import
+ */
+ async setSocialCSVAccounts(accountsData) {
+ try {
+ const locationId = this.config.locationId;
+ const response = await this.axiosInstance.post(`/social-media-posting/${locationId}/set-accounts`, accountsData);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Get CSV Posts
+ */
+ async getSocialCSVPosts(csvId, skip, limit) {
+ try {
+ const locationId = this.config.locationId;
+ const params = {};
+ if (skip !== undefined)
+ params.skip = skip.toString();
+ if (limit !== undefined)
+ params.limit = limit.toString();
+ const response = await this.axiosInstance.get(`/social-media-posting/${locationId}/csv/${csvId}`, { params });
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Start CSV Finalization
+ */
+ async finalizeSocialCSV(csvId, finalizeData) {
+ try {
+ const locationId = this.config.locationId;
+ const response = await this.axiosInstance.patch(`/social-media-posting/${locationId}/csv/${csvId}`, finalizeData);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Delete CSV Import
+ */
+ async deleteSocialCSV(csvId) {
+ try {
+ const locationId = this.config.locationId;
+ const response = await this.axiosInstance.delete(`/social-media-posting/${locationId}/csv/${csvId}`);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Delete CSV Post
+ */
+ async deleteSocialCSVPost(csvId, postId) {
+ try {
+ const locationId = this.config.locationId;
+ const response = await this.axiosInstance.delete(`/social-media-posting/${locationId}/csv/${csvId}/post/${postId}`);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ // ===== CATEGORIES & TAGS =====
+ /**
+ * Get Social Media Categories
+ */
+ async getSocialCategories(searchText, limit, skip) {
+ // TODO: Implement this method properly
+ throw new Error('Method not yet implemented');
+ }
+ // TODO: Implement remaining social media API methods
+ async getSocialCategory(categoryId) {
+ throw new Error('Method not yet implemented');
+ }
+ async getSocialTags(searchText, limit, skip) {
+ throw new Error('Method not yet implemented');
+ }
+ async getSocialTagsByIds(tagData) {
+ throw new Error('Method not yet implemented');
+ }
+ async startSocialOAuth(platform, userId, page, reconnect) {
+ throw new Error('Method not yet implemented');
+ }
+ async getGoogleBusinessLocations(accountId) {
+ throw new Error('Method not yet implemented');
+ }
+ async setGoogleBusinessLocations(accountId, locationData) {
+ throw new Error('Method not yet implemented');
+ }
+ async getFacebookPages(accountId) {
+ throw new Error('Method not yet implemented');
+ }
+ async attachFacebookPages(accountId, pageData) {
+ throw new Error('Method not yet implemented');
+ }
+ async getInstagramAccounts(accountId) {
+ throw new Error('Method not yet implemented');
+ }
+ async attachInstagramAccounts(accountId, accountData) {
+ throw new Error('Method not yet implemented');
+ }
+ async getLinkedInAccounts(accountId) {
+ throw new Error('Method not yet implemented');
+ }
+ async attachLinkedInAccounts(accountId, accountData) {
+ throw new Error('Method not yet implemented');
+ }
+ async getTwitterProfile(accountId) {
+ throw new Error('Method not yet implemented');
+ }
+ async attachTwitterProfile(accountId, profileData) {
+ throw new Error('Method not yet implemented');
+ }
+ async getTikTokProfile(accountId) {
+ throw new Error('Method not yet implemented');
+ }
+ async attachTikTokProfile(accountId, profileData) {
+ throw new Error('Method not yet implemented');
+ }
+ async getTikTokBusinessProfile(accountId) {
+ throw new Error('Method not yet implemented');
+ }
+ // ===== MISSING CALENDAR GROUPS MANAGEMENT METHODS =====
+ /**
+ * Validate calendar group slug
+ * GET /calendars/groups/slug/validate
+ */
+ async validateCalendarGroupSlug(slug, locationId) {
+ try {
+ const params = {
+ locationId: locationId || this.config.locationId,
+ slug
+ };
+ const response = await this.axiosInstance.get('/calendars/groups/slug/validate', { params });
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ /**
+ * Update calendar group by ID
+ * PUT /calendars/groups/{groupId}
+ */
+ async updateCalendarGroup(groupId, updateData) {
+ try {
+ const response = await this.axiosInstance.put(`/calendars/groups/${groupId}`, updateData);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ /**
+ * Delete calendar group by ID
+ * DELETE /calendars/groups/{groupId}
+ */
+ async deleteCalendarGroup(groupId) {
+ try {
+ const response = await this.axiosInstance.delete(`/calendars/groups/${groupId}`);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ /**
+ * Disable calendar group
+ * POST /calendars/groups/{groupId}/status
+ */
+ async disableCalendarGroup(groupId, isActive) {
+ try {
+ const payload = { isActive };
+ const response = await this.axiosInstance.post(`/calendars/groups/${groupId}/status`, payload);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ // ===== APPOINTMENT NOTES METHODS =====
+ /**
+ * Get appointment notes
+ * GET /calendars/events/appointments/{appointmentId}/notes
+ */
+ async getAppointmentNotes(appointmentId, limit = 10, offset = 0) {
+ try {
+ const params = { limit, offset };
+ const response = await this.axiosInstance.get(`/calendars/events/appointments/${appointmentId}/notes`, { params });
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ /**
+ * Create appointment note
+ * POST /calendars/events/appointments/{appointmentId}/notes
+ */
+ async createAppointmentNote(appointmentId, noteData) {
+ try {
+ const response = await this.axiosInstance.post(`/calendars/events/appointments/${appointmentId}/notes`, noteData);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ /**
+ * Update appointment note
+ * PUT /calendars/events/appointments/{appointmentId}/notes/{noteId}
+ */
+ async updateAppointmentNote(appointmentId, noteId, updateData) {
+ try {
+ const response = await this.axiosInstance.put(`/calendars/events/appointments/${appointmentId}/notes/${noteId}`, updateData);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ /**
+ * Delete appointment note
+ * DELETE /calendars/events/appointments/{appointmentId}/notes/{noteId}
+ */
+ async deleteAppointmentNote(appointmentId, noteId) {
+ try {
+ const response = await this.axiosInstance.delete(`/calendars/events/appointments/${appointmentId}/notes/${noteId}`);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ // ===== CALENDAR RESOURCES METHODS =====
+ /**
+ * Get calendar resources
+ * GET /calendars/resources/{resourceType}
+ */
+ async getCalendarResources(resourceType, limit = 20, skip = 0, locationId) {
+ try {
+ const params = {
+ locationId: locationId || this.config.locationId,
+ limit,
+ skip
+ };
+ const response = await this.axiosInstance.get(`/calendars/resources/${resourceType}`, { params });
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ /**
+ * Create calendar resource
+ * POST /calendars/resources/{resourceType}
+ */
+ async createCalendarResource(resourceType, resourceData) {
+ try {
+ const payload = {
+ ...resourceData,
+ locationId: resourceData.locationId || this.config.locationId
+ };
+ const response = await this.axiosInstance.post(`/calendars/resources/${resourceType}`, payload);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ /**
+ * Get calendar resource by ID
+ * GET /calendars/resources/{resourceType}/{resourceId}
+ */
+ async getCalendarResource(resourceType, resourceId) {
+ try {
+ const response = await this.axiosInstance.get(`/calendars/resources/${resourceType}/${resourceId}`);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ /**
+ * Update calendar resource
+ * PUT /calendars/resources/{resourceType}/{resourceId}
+ */
+ async updateCalendarResource(resourceType, resourceId, updateData) {
+ try {
+ const response = await this.axiosInstance.put(`/calendars/resources/${resourceType}/${resourceId}`, updateData);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ /**
+ * Delete calendar resource
+ * DELETE /calendars/resources/{resourceType}/{resourceId}
+ */
+ async deleteCalendarResource(resourceType, resourceId) {
+ try {
+ const response = await this.axiosInstance.delete(`/calendars/resources/${resourceType}/${resourceId}`);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ // ===== CALENDAR NOTIFICATIONS METHODS =====
+ /**
+ * Get calendar notifications
+ * GET /calendars/{calendarId}/notifications
+ */
+ async getCalendarNotifications(calendarId, queryParams) {
+ try {
+ const params = {
+ ...queryParams
+ };
+ const response = await this.axiosInstance.get(`/calendars/${calendarId}/notifications`, { params });
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ /**
+ * Create calendar notifications
+ * POST /calendars/{calendarId}/notifications
+ */
+ async createCalendarNotifications(calendarId, notifications) {
+ try {
+ const payload = { notifications };
+ const response = await this.axiosInstance.post(`/calendars/${calendarId}/notifications`, payload);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ /**
+ * Get calendar notification by ID
+ * GET /calendars/{calendarId}/notifications/{notificationId}
+ */
+ async getCalendarNotification(calendarId, notificationId) {
+ try {
+ const response = await this.axiosInstance.get(`/calendars/${calendarId}/notifications/${notificationId}`);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ /**
+ * Update calendar notification
+ * PUT /calendars/{calendarId}/notifications/{notificationId}
+ */
+ async updateCalendarNotification(calendarId, notificationId, updateData) {
+ try {
+ const response = await this.axiosInstance.put(`/calendars/${calendarId}/notifications/${notificationId}`, updateData);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ /**
+ * Delete calendar notification
+ * DELETE /calendars/{calendarId}/notifications/{notificationId}
+ */
+ async deleteCalendarNotification(calendarId, notificationId) {
+ try {
+ const response = await this.axiosInstance.delete(`/calendars/${calendarId}/notifications/${notificationId}`);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ /**
+ * Get blocked slots by location
+ * GET /calendars/blocked-slots
+ */
+ async getBlockedSlotsByLocation(slotParams) {
+ try {
+ const params = new URLSearchParams({
+ locationId: slotParams.locationId,
+ startTime: slotParams.startTime,
+ endTime: slotParams.endTime,
+ ...(slotParams.userId && { userId: slotParams.userId }),
+ ...(slotParams.calendarId && { calendarId: slotParams.calendarId }),
+ ...(slotParams.groupId && { groupId: slotParams.groupId })
+ });
+ const response = await this.axiosInstance.get(`/calendars/blocked-slots?${params}`);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ /**
+ * Create a new block slot
+ * POST /calendars/blocked-slots
+ */
+ async createBlockSlot(blockSlotData) {
+ try {
+ const payload = {
+ ...blockSlotData,
+ locationId: blockSlotData.locationId || this.config.locationId
+ };
+ const response = await this.axiosInstance.post('/calendars/blocked-slots', payload);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ // ===== MEDIA LIBRARY API METHODS =====
+ /**
+ * Get list of files and folders from media library
+ * GET /medias/files
+ */
+ async getMediaFiles(params) {
+ try {
+ const queryParams = new URLSearchParams({
+ sortBy: params.sortBy,
+ sortOrder: params.sortOrder,
+ altType: params.altType,
+ altId: params.altId,
+ ...(params.offset !== undefined && { offset: params.offset.toString() }),
+ ...(params.limit !== undefined && { limit: params.limit.toString() }),
+ ...(params.type && { type: params.type }),
+ ...(params.query && { query: params.query }),
+ ...(params.parentId && { parentId: params.parentId })
+ });
+ const response = await this.axiosInstance.get(`/medias/files?${queryParams}`);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ /**
+ * Upload file to media library
+ * POST /medias/upload-file
+ */
+ async uploadMediaFile(uploadData) {
+ try {
+ const formData = new FormData();
+ // Handle file upload (either direct file or hosted file URL)
+ if (uploadData.hosted && uploadData.fileUrl) {
+ formData.append('hosted', 'true');
+ formData.append('fileUrl', uploadData.fileUrl);
+ }
+ else if (uploadData.file) {
+ formData.append('hosted', 'false');
+ formData.append('file', uploadData.file);
+ }
+ else {
+ throw new Error('Either file or fileUrl (with hosted=true) must be provided');
+ }
+ // Add optional fields
+ if (uploadData.name) {
+ formData.append('name', uploadData.name);
+ }
+ if (uploadData.parentId) {
+ formData.append('parentId', uploadData.parentId);
+ }
+ const response = await this.axiosInstance.post('/medias/upload-file', formData, {
+ headers: {
+ 'Content-Type': 'multipart/form-data',
+ }
+ });
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ /**
+ * Delete file or folder from media library
+ * DELETE /medias/{id}
+ */
+ async deleteMediaFile(deleteParams) {
+ try {
+ const queryParams = new URLSearchParams({
+ altType: deleteParams.altType,
+ altId: deleteParams.altId
+ });
+ const response = await this.axiosInstance.delete(`/medias/${deleteParams.id}?${queryParams}`);
+ return this.wrapResponse({ success: true, message: 'Media file deleted successfully' });
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ // ===== CUSTOM OBJECTS API METHODS =====
+ /**
+ * Get all objects for a location
+ * GET /objects/
+ */
+ async getObjectsByLocation(locationId) {
+ try {
+ const params = {
+ locationId: locationId || this.config.locationId
+ };
+ const response = await this.axiosInstance.get('/objects/', { params });
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ /**
+ * Create custom object schema
+ * POST /objects/
+ */
+ async createObjectSchema(schemaData) {
+ try {
+ const payload = {
+ ...schemaData,
+ locationId: schemaData.locationId || this.config.locationId
+ };
+ const response = await this.axiosInstance.post('/objects/', payload);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ /**
+ * Get object schema by key/id
+ * GET /objects/{key}
+ */
+ async getObjectSchema(params) {
+ try {
+ const queryParams = {
+ locationId: params.locationId || this.config.locationId,
+ ...(params.fetchProperties !== undefined && { fetchProperties: params.fetchProperties.toString() })
+ };
+ const response = await this.axiosInstance.get(`/objects/${params.key}`, { params: queryParams });
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ /**
+ * Update object schema by key/id
+ * PUT /objects/{key}
+ */
+ async updateObjectSchema(key, updateData) {
+ try {
+ const payload = {
+ ...updateData,
+ locationId: updateData.locationId || this.config.locationId
+ };
+ const response = await this.axiosInstance.put(`/objects/${key}`, payload);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ /**
+ * Create object record
+ * POST /objects/{schemaKey}/records
+ */
+ async createObjectRecord(schemaKey, recordData) {
+ try {
+ const payload = {
+ ...recordData,
+ locationId: recordData.locationId || this.config.locationId
+ };
+ const response = await this.axiosInstance.post(`/objects/${schemaKey}/records`, payload);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ /**
+ * Get object record by id
+ * GET /objects/{schemaKey}/records/{id}
+ */
+ async getObjectRecord(schemaKey, recordId) {
+ try {
+ const response = await this.axiosInstance.get(`/objects/${schemaKey}/records/${recordId}`);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ /**
+ * Update object record
+ * PUT /objects/{schemaKey}/records/{id}
+ */
+ async updateObjectRecord(schemaKey, recordId, updateData) {
+ try {
+ const queryParams = {
+ locationId: updateData.locationId || this.config.locationId
+ };
+ const response = await this.axiosInstance.put(`/objects/${schemaKey}/records/${recordId}`, updateData, { params: queryParams });
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ /**
+ * Delete object record
+ * DELETE /objects/{schemaKey}/records/{id}
+ */
+ async deleteObjectRecord(schemaKey, recordId) {
+ try {
+ const response = await this.axiosInstance.delete(`/objects/${schemaKey}/records/${recordId}`);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ /**
+ * Search object records
+ * POST /objects/{schemaKey}/records/search
+ */
+ async searchObjectRecords(schemaKey, searchData) {
+ try {
+ const payload = {
+ ...searchData,
+ locationId: searchData.locationId || this.config.locationId
+ };
+ const response = await this.axiosInstance.post(`/objects/${schemaKey}/records/search`, payload);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ // ===== ASSOCIATIONS API METHODS =====
+ /**
+ * Get all associations for a location
+ * GET /associations/
+ */
+ async getAssociations(params) {
+ try {
+ const queryParams = {
+ locationId: params.locationId || this.config.locationId,
+ skip: params.skip.toString(),
+ limit: params.limit.toString()
+ };
+ const response = await this.axiosInstance.get('/associations/', { params: queryParams });
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ /**
+ * Create association
+ * POST /associations/
+ */
+ async createAssociation(associationData) {
+ try {
+ const payload = {
+ ...associationData,
+ locationId: associationData.locationId || this.config.locationId
+ };
+ const response = await this.axiosInstance.post('/associations/', payload);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ /**
+ * Get association by ID
+ * GET /associations/{associationId}
+ */
+ async getAssociationById(associationId) {
+ try {
+ const response = await this.axiosInstance.get(`/associations/${associationId}`);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ /**
+ * Update association
+ * PUT /associations/{associationId}
+ */
+ async updateAssociation(associationId, updateData) {
+ try {
+ const response = await this.axiosInstance.put(`/associations/${associationId}`, updateData);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ /**
+ * Delete association
+ * DELETE /associations/{associationId}
+ */
+ async deleteAssociation(associationId) {
+ try {
+ const response = await this.axiosInstance.delete(`/associations/${associationId}`);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ /**
+ * Get association by key name
+ * GET /associations/key/{key_name}
+ */
+ async getAssociationByKey(params) {
+ try {
+ const queryParams = {
+ locationId: params.locationId || this.config.locationId
+ };
+ const response = await this.axiosInstance.get(`/associations/key/${params.keyName}`, { params: queryParams });
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ /**
+ * Get association by object key
+ * GET /associations/objectKey/{objectKey}
+ */
+ async getAssociationByObjectKey(params) {
+ try {
+ const queryParams = params.locationId ? {
+ locationId: params.locationId
+ } : {};
+ const response = await this.axiosInstance.get(`/associations/objectKey/${params.objectKey}`, { params: queryParams });
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ /**
+ * Create relation between entities
+ * POST /associations/relations
+ */
+ async createRelation(relationData) {
+ try {
+ const payload = {
+ ...relationData,
+ locationId: relationData.locationId || this.config.locationId
+ };
+ const response = await this.axiosInstance.post('/associations/relations', payload);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ /**
+ * Get relations by record ID
+ * GET /associations/relations/{recordId}
+ */
+ async getRelationsByRecord(params) {
+ try {
+ const queryParams = {
+ locationId: params.locationId || this.config.locationId,
+ skip: params.skip.toString(),
+ limit: params.limit.toString(),
+ ...(params.associationIds && { associationIds: params.associationIds })
+ };
+ const response = await this.axiosInstance.get(`/associations/relations/${params.recordId}`, { params: queryParams });
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ /**
+ * Delete relation
+ * DELETE /associations/relations/{relationId}
+ */
+ async deleteRelation(params) {
+ try {
+ const queryParams = {
+ locationId: params.locationId || this.config.locationId
+ };
+ const response = await this.axiosInstance.delete(`/associations/relations/${params.relationId}`, { params: queryParams });
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ // ===== CUSTOM FIELDS V2 API METHODS =====
+ /**
+ * Get custom field or folder by ID
+ * GET /custom-fields/{id}
+ */
+ async getCustomFieldV2ById(id) {
+ try {
+ const response = await this.axiosInstance.get(`/custom-fields/${id}`);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ /**
+ * Create custom field
+ * POST /custom-fields/
+ */
+ async createCustomFieldV2(fieldData) {
+ try {
+ const payload = {
+ ...fieldData,
+ locationId: fieldData.locationId || this.config.locationId
+ };
+ const response = await this.axiosInstance.post('/custom-fields/', payload);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ /**
+ * Update custom field by ID
+ * PUT /custom-fields/{id}
+ */
+ async updateCustomFieldV2(id, fieldData) {
+ try {
+ const payload = {
+ ...fieldData,
+ locationId: fieldData.locationId || this.config.locationId
+ };
+ const response = await this.axiosInstance.put(`/custom-fields/${id}`, payload);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ /**
+ * Delete custom field by ID
+ * DELETE /custom-fields/{id}
+ */
+ async deleteCustomFieldV2(id) {
+ try {
+ const response = await this.axiosInstance.delete(`/custom-fields/${id}`);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ /**
+ * Get custom fields by object key
+ * GET /custom-fields/object-key/{objectKey}
+ */
+ async getCustomFieldsV2ByObjectKey(params) {
+ try {
+ const queryParams = {
+ locationId: params.locationId || this.config.locationId
+ };
+ const response = await this.axiosInstance.get(`/custom-fields/object-key/${params.objectKey}`, { params: queryParams });
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ /**
+ * Create custom field folder
+ * POST /custom-fields/folder
+ */
+ async createCustomFieldV2Folder(folderData) {
+ try {
+ const payload = {
+ ...folderData,
+ locationId: folderData.locationId || this.config.locationId
+ };
+ const response = await this.axiosInstance.post('/custom-fields/folder', payload);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ /**
+ * Update custom field folder name
+ * PUT /custom-fields/folder/{id}
+ */
+ async updateCustomFieldV2Folder(id, folderData) {
+ try {
+ const payload = {
+ ...folderData,
+ locationId: folderData.locationId || this.config.locationId
+ };
+ const response = await this.axiosInstance.put(`/custom-fields/folder/${id}`, payload);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ /**
+ * Delete custom field folder
+ * DELETE /custom-fields/folder/{id}
+ */
+ async deleteCustomFieldV2Folder(params) {
+ try {
+ const queryParams = {
+ locationId: params.locationId || this.config.locationId
+ };
+ const response = await this.axiosInstance.delete(`/custom-fields/folder/${params.id}`, { params: queryParams });
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ // ===== WORKFLOWS API METHODS =====
+ /**
+ * Get all workflows for a location
+ * GET /workflows/
+ */
+ async getWorkflows(request) {
+ try {
+ const queryParams = {
+ locationId: request.locationId || this.config.locationId
+ };
+ const response = await this.axiosInstance.get('/workflows/', { params: queryParams });
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ // ===== SURVEYS API METHODS =====
+ /**
+ * Get all surveys for a location
+ * GET /surveys/
+ */
+ async getSurveys(request) {
+ try {
+ const queryParams = {
+ locationId: request.locationId || this.config.locationId
+ };
+ if (request.skip !== undefined) {
+ queryParams.skip = request.skip.toString();
+ }
+ if (request.limit !== undefined) {
+ queryParams.limit = request.limit.toString();
+ }
+ if (request.type) {
+ queryParams.type = request.type;
+ }
+ const response = await this.axiosInstance.get('/surveys/', { params: queryParams });
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw this.handleApiError(error);
+ }
+ }
+ /**
+ * Get survey submissions with filtering and pagination
+ * GET /surveys/submissions
+ */
+ async getSurveySubmissions(request) {
+ try {
+ const locationId = request.locationId || this.config.locationId;
+ const params = new URLSearchParams();
+ if (request.page)
+ params.append('page', request.page.toString());
+ if (request.limit)
+ params.append('limit', request.limit.toString());
+ if (request.surveyId)
+ params.append('surveyId', request.surveyId);
+ if (request.q)
+ params.append('q', request.q);
+ if (request.startAt)
+ params.append('startAt', request.startAt);
+ if (request.endAt)
+ params.append('endAt', request.endAt);
+ const response = await this.axiosInstance.get(`/locations/${locationId}/surveys/submissions?${params.toString()}`);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ // ===== STORE API METHODS =====
+ /**
+ * SHIPPING ZONES API METHODS
+ */
+ /**
+ * Create a new shipping zone
+ * POST /store/shipping-zone
+ */
+ async createShippingZone(zoneData) {
+ try {
+ const payload = {
+ ...zoneData,
+ altId: zoneData.altId || this.config.locationId,
+ altType: 'location'
+ };
+ const response = await this.axiosInstance.post('/store/shipping-zone', payload);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * List all shipping zones
+ * GET /store/shipping-zone
+ */
+ async listShippingZones(params) {
+ try {
+ const altId = params.altId || this.config.locationId;
+ const queryParams = new URLSearchParams({
+ altId,
+ altType: 'location'
+ });
+ if (params.limit)
+ queryParams.append('limit', params.limit.toString());
+ if (params.offset)
+ queryParams.append('offset', params.offset.toString());
+ if (params.withShippingRate !== undefined)
+ queryParams.append('withShippingRate', params.withShippingRate.toString());
+ const response = await this.axiosInstance.get(`/store/shipping-zone?${queryParams.toString()}`);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Get a specific shipping zone by ID
+ * GET /store/shipping-zone/{shippingZoneId}
+ */
+ async getShippingZone(shippingZoneId, params) {
+ try {
+ const altId = params.altId || this.config.locationId;
+ const queryParams = new URLSearchParams({
+ altId,
+ altType: 'location'
+ });
+ if (params.withShippingRate !== undefined)
+ queryParams.append('withShippingRate', params.withShippingRate.toString());
+ const response = await this.axiosInstance.get(`/store/shipping-zone/${shippingZoneId}?${queryParams.toString()}`);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Update a shipping zone
+ * PUT /store/shipping-zone/{shippingZoneId}
+ */
+ async updateShippingZone(shippingZoneId, updateData) {
+ try {
+ const payload = {
+ ...updateData,
+ altId: updateData.altId || this.config.locationId,
+ altType: 'location'
+ };
+ const response = await this.axiosInstance.put(`/store/shipping-zone/${shippingZoneId}`, payload);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Delete a shipping zone
+ * DELETE /store/shipping-zone/{shippingZoneId}
+ */
+ async deleteShippingZone(shippingZoneId, params) {
+ try {
+ const altId = params.altId || this.config.locationId;
+ const queryParams = new URLSearchParams({
+ altId,
+ altType: 'location'
+ });
+ const response = await this.axiosInstance.delete(`/store/shipping-zone/${shippingZoneId}?${queryParams.toString()}`);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * SHIPPING RATES API METHODS
+ */
+ /**
+ * Get available shipping rates for an order
+ * POST /store/shipping-zone/shipping-rates
+ */
+ async getAvailableShippingRates(rateData) {
+ try {
+ const payload = {
+ ...rateData,
+ altId: rateData.altId || this.config.locationId,
+ altType: 'location'
+ };
+ const response = await this.axiosInstance.post('/store/shipping-zone/shipping-rates', payload);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Create a new shipping rate for a zone
+ * POST /store/shipping-zone/{shippingZoneId}/shipping-rate
+ */
+ async createShippingRate(shippingZoneId, rateData) {
+ try {
+ const payload = {
+ ...rateData,
+ altId: rateData.altId || this.config.locationId,
+ altType: 'location'
+ };
+ const response = await this.axiosInstance.post(`/store/shipping-zone/${shippingZoneId}/shipping-rate`, payload);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * List shipping rates for a zone
+ * GET /store/shipping-zone/{shippingZoneId}/shipping-rate
+ */
+ async listShippingRates(shippingZoneId, params) {
+ try {
+ const altId = params.altId || this.config.locationId;
+ const queryParams = new URLSearchParams({
+ altId,
+ altType: 'location'
+ });
+ if (params.limit)
+ queryParams.append('limit', params.limit.toString());
+ if (params.offset)
+ queryParams.append('offset', params.offset.toString());
+ const response = await this.axiosInstance.get(`/store/shipping-zone/${shippingZoneId}/shipping-rate?${queryParams.toString()}`);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Get a specific shipping rate
+ * GET /store/shipping-zone/{shippingZoneId}/shipping-rate/{shippingRateId}
+ */
+ async getShippingRate(shippingZoneId, shippingRateId, params) {
+ try {
+ const altId = params.altId || this.config.locationId;
+ const queryParams = new URLSearchParams({
+ altId,
+ altType: 'location'
+ });
+ const response = await this.axiosInstance.get(`/store/shipping-zone/${shippingZoneId}/shipping-rate/${shippingRateId}?${queryParams.toString()}`);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Update a shipping rate
+ * PUT /store/shipping-zone/{shippingZoneId}/shipping-rate/{shippingRateId}
+ */
+ async updateShippingRate(shippingZoneId, shippingRateId, updateData) {
+ try {
+ const payload = {
+ ...updateData,
+ altId: updateData.altId || this.config.locationId,
+ altType: 'location'
+ };
+ const response = await this.axiosInstance.put(`/store/shipping-zone/${shippingZoneId}/shipping-rate/${shippingRateId}`, payload);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Delete a shipping rate
+ * DELETE /store/shipping-zone/{shippingZoneId}/shipping-rate/{shippingRateId}
+ */
+ async deleteShippingRate(shippingZoneId, shippingRateId, params) {
+ try {
+ const altId = params.altId || this.config.locationId;
+ const queryParams = new URLSearchParams({
+ altId,
+ altType: 'location'
+ });
+ const response = await this.axiosInstance.delete(`/store/shipping-zone/${shippingZoneId}/shipping-rate/${shippingRateId}?${queryParams.toString()}`);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * SHIPPING CARRIERS API METHODS
+ */
+ /**
+ * Create a new shipping carrier
+ * POST /store/shipping-carrier
+ */
+ async createShippingCarrier(carrierData) {
+ try {
+ const payload = {
+ ...carrierData,
+ altId: carrierData.altId || this.config.locationId,
+ altType: 'location'
+ };
+ const response = await this.axiosInstance.post('/store/shipping-carrier', payload);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * List all shipping carriers
+ * GET /store/shipping-carrier
+ */
+ async listShippingCarriers(params) {
+ try {
+ const altId = params.altId || this.config.locationId;
+ const queryParams = new URLSearchParams({
+ altId,
+ altType: 'location'
+ });
+ const response = await this.axiosInstance.get(`/store/shipping-carrier?${queryParams.toString()}`);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Get a specific shipping carrier by ID
+ * GET /store/shipping-carrier/{shippingCarrierId}
+ */
+ async getShippingCarrier(shippingCarrierId, params) {
+ try {
+ const altId = params.altId || this.config.locationId;
+ const queryParams = new URLSearchParams({
+ altId,
+ altType: 'location'
+ });
+ const response = await this.axiosInstance.get(`/store/shipping-carrier/${shippingCarrierId}?${queryParams.toString()}`);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Update a shipping carrier
+ * PUT /store/shipping-carrier/{shippingCarrierId}
+ */
+ async updateShippingCarrier(shippingCarrierId, updateData) {
+ try {
+ const payload = {
+ ...updateData,
+ altId: updateData.altId || this.config.locationId,
+ altType: 'location'
+ };
+ const response = await this.axiosInstance.put(`/store/shipping-carrier/${shippingCarrierId}`, payload);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Delete a shipping carrier
+ * DELETE /store/shipping-carrier/{shippingCarrierId}
+ */
+ async deleteShippingCarrier(shippingCarrierId, params) {
+ try {
+ const altId = params.altId || this.config.locationId;
+ const queryParams = new URLSearchParams({
+ altId,
+ altType: 'location'
+ });
+ const response = await this.axiosInstance.delete(`/store/shipping-carrier/${shippingCarrierId}?${queryParams.toString()}`);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * STORE SETTINGS API METHODS
+ */
+ /**
+ * Create or update store settings
+ * POST /store/store-setting
+ */
+ async createStoreSetting(settingData) {
+ try {
+ const payload = {
+ ...settingData,
+ altId: settingData.altId || this.config.locationId,
+ altType: 'location'
+ };
+ const response = await this.axiosInstance.post('/store/store-setting', payload);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Get store settings
+ * GET /store/store-setting
+ */
+ async getStoreSetting(params) {
+ try {
+ const altId = params.altId || this.config.locationId;
+ const queryParams = new URLSearchParams({
+ altId,
+ altType: 'location'
+ });
+ const response = await this.axiosInstance.get(`/store/store-setting?${queryParams.toString()}`);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * PRODUCTS API METHODS
+ */
+ /**
+ * Create a new product
+ * POST /products/
+ */
+ async createProduct(productData) {
+ try {
+ const response = await this.axiosInstance.post('/products/', productData);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Update a product by ID
+ * PUT /products/{productId}
+ */
+ async updateProduct(productId, updateData) {
+ try {
+ const response = await this.axiosInstance.put(`/products/${productId}`, updateData);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Get a product by ID
+ * GET /products/{productId}
+ */
+ async getProduct(productId, locationId) {
+ try {
+ const queryParams = new URLSearchParams({
+ locationId: locationId || this.config.locationId
+ });
+ const response = await this.axiosInstance.get(`/products/${productId}?${queryParams.toString()}`);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * List products
+ * GET /products/
+ */
+ async listProducts(params) {
+ try {
+ const queryParams = new URLSearchParams({
+ locationId: params.locationId || this.config.locationId
+ });
+ if (params.limit)
+ queryParams.append('limit', params.limit.toString());
+ if (params.offset)
+ queryParams.append('offset', params.offset.toString());
+ if (params.search)
+ queryParams.append('search', params.search);
+ if (params.collectionIds?.length)
+ queryParams.append('collectionIds', params.collectionIds.join(','));
+ if (params.collectionSlug)
+ queryParams.append('collectionSlug', params.collectionSlug);
+ if (params.expand?.length)
+ params.expand.forEach(item => queryParams.append('expand', item));
+ if (params.productIds?.length)
+ params.productIds.forEach(id => queryParams.append('productIds', id));
+ if (params.storeId)
+ queryParams.append('storeId', params.storeId);
+ if (params.includedInStore !== undefined)
+ queryParams.append('includedInStore', params.includedInStore.toString());
+ if (params.availableInStore !== undefined)
+ queryParams.append('availableInStore', params.availableInStore.toString());
+ if (params.sortOrder)
+ queryParams.append('sortOrder', params.sortOrder);
+ const response = await this.axiosInstance.get(`/products/?${queryParams.toString()}`);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Delete a product by ID
+ * DELETE /products/{productId}
+ */
+ async deleteProduct(productId, locationId) {
+ try {
+ const queryParams = new URLSearchParams({
+ locationId: locationId || this.config.locationId
+ });
+ const response = await this.axiosInstance.delete(`/products/${productId}?${queryParams.toString()}`);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Bulk update products
+ * POST /products/bulk-update
+ */
+ async bulkUpdateProducts(updateData) {
+ try {
+ const payload = {
+ ...updateData,
+ altId: updateData.altId || this.config.locationId,
+ altType: 'location'
+ };
+ const response = await this.axiosInstance.post('/products/bulk-update', payload);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Create a price for a product
+ * POST /products/{productId}/price
+ */
+ async createPrice(productId, priceData) {
+ try {
+ const payload = {
+ ...priceData,
+ locationId: priceData.locationId || this.config.locationId
+ };
+ const response = await this.axiosInstance.post(`/products/${productId}/price`, payload);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Update a price by ID
+ * PUT /products/{productId}/price/{priceId}
+ */
+ async updatePrice(productId, priceId, updateData) {
+ try {
+ const payload = {
+ ...updateData,
+ locationId: updateData.locationId || this.config.locationId
+ };
+ const response = await this.axiosInstance.put(`/products/${productId}/price/${priceId}`, payload);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Get a price by ID
+ * GET /products/{productId}/price/{priceId}
+ */
+ async getPrice(productId, priceId, locationId) {
+ try {
+ const queryParams = new URLSearchParams({
+ locationId: locationId || this.config.locationId
+ });
+ const response = await this.axiosInstance.get(`/products/${productId}/price/${priceId}?${queryParams.toString()}`);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * List prices for a product
+ * GET /products/{productId}/price
+ */
+ async listPrices(productId, params) {
+ try {
+ const queryParams = new URLSearchParams({
+ locationId: params.locationId || this.config.locationId
+ });
+ if (params.limit)
+ queryParams.append('limit', params.limit.toString());
+ if (params.offset)
+ queryParams.append('offset', params.offset.toString());
+ if (params.ids)
+ queryParams.append('ids', params.ids);
+ const response = await this.axiosInstance.get(`/products/${productId}/price?${queryParams.toString()}`);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Delete a price by ID
+ * DELETE /products/{productId}/price/{priceId}
+ */
+ async deletePrice(productId, priceId, locationId) {
+ try {
+ const queryParams = new URLSearchParams({
+ locationId: locationId || this.config.locationId
+ });
+ const response = await this.axiosInstance.delete(`/products/${productId}/price/${priceId}?${queryParams.toString()}`);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * List inventory
+ * GET /products/inventory
+ */
+ async listInventory(params) {
+ try {
+ const queryParams = new URLSearchParams({
+ altId: params.altId || this.config.locationId,
+ altType: 'location'
+ });
+ if (params.limit)
+ queryParams.append('limit', params.limit.toString());
+ if (params.offset)
+ queryParams.append('offset', params.offset.toString());
+ if (params.search)
+ queryParams.append('search', params.search);
+ const response = await this.axiosInstance.get(`/products/inventory?${queryParams.toString()}`);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Update inventory
+ * POST /products/inventory
+ */
+ async updateInventory(updateData) {
+ try {
+ const payload = {
+ ...updateData,
+ altId: updateData.altId || this.config.locationId,
+ altType: 'location'
+ };
+ const response = await this.axiosInstance.post('/products/inventory', payload);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Get product store stats
+ * GET /products/store/{storeId}/stats
+ */
+ async getProductStoreStats(storeId, params) {
+ try {
+ const queryParams = new URLSearchParams({
+ altId: params.altId || this.config.locationId,
+ altType: 'location'
+ });
+ if (params.search)
+ queryParams.append('search', params.search);
+ if (params.collectionIds)
+ queryParams.append('collectionIds', params.collectionIds);
+ const response = await this.axiosInstance.get(`/products/store/${storeId}/stats?${queryParams.toString()}`);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Update product store status
+ * POST /products/store/{storeId}
+ */
+ async updateProductStore(storeId, updateData) {
+ try {
+ const response = await this.axiosInstance.post(`/products/store/${storeId}`, updateData);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Create a product collection
+ * POST /products/collections
+ */
+ async createProductCollection(collectionData) {
+ try {
+ const payload = {
+ ...collectionData,
+ altId: collectionData.altId || this.config.locationId,
+ altType: 'location'
+ };
+ const response = await this.axiosInstance.post('/products/collections', payload);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Update a product collection
+ * PUT /products/collections/{collectionId}
+ */
+ async updateProductCollection(collectionId, updateData) {
+ try {
+ const payload = {
+ ...updateData,
+ altId: updateData.altId || this.config.locationId,
+ altType: 'location'
+ };
+ const response = await this.axiosInstance.put(`/products/collections/${collectionId}`, payload);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Get a product collection by ID
+ * GET /products/collections/{collectionId}
+ */
+ async getProductCollection(collectionId) {
+ try {
+ const response = await this.axiosInstance.get(`/products/collections/${collectionId}`);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * List product collections
+ * GET /products/collections
+ */
+ async listProductCollections(params) {
+ try {
+ const queryParams = new URLSearchParams({
+ altId: params.altId || this.config.locationId,
+ altType: 'location'
+ });
+ if (params.limit)
+ queryParams.append('limit', params.limit.toString());
+ if (params.offset)
+ queryParams.append('offset', params.offset.toString());
+ if (params.collectionIds)
+ queryParams.append('collectionIds', params.collectionIds);
+ if (params.name)
+ queryParams.append('name', params.name);
+ const response = await this.axiosInstance.get(`/products/collections?${queryParams.toString()}`);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Delete a product collection
+ * DELETE /products/collections/{collectionId}
+ */
+ async deleteProductCollection(collectionId, params) {
+ try {
+ const queryParams = new URLSearchParams({
+ altId: params.altId || this.config.locationId,
+ altType: 'location'
+ });
+ const response = await this.axiosInstance.delete(`/products/collections/${collectionId}?${queryParams.toString()}`);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * List product reviews
+ * GET /products/reviews
+ */
+ async listProductReviews(params) {
+ try {
+ const queryParams = new URLSearchParams({
+ altId: params.altId || this.config.locationId,
+ altType: 'location'
+ });
+ if (params.limit)
+ queryParams.append('limit', params.limit.toString());
+ if (params.offset)
+ queryParams.append('offset', params.offset.toString());
+ if (params.sortField)
+ queryParams.append('sortField', params.sortField);
+ if (params.sortOrder)
+ queryParams.append('sortOrder', params.sortOrder);
+ if (params.rating)
+ queryParams.append('rating', params.rating.toString());
+ if (params.startDate)
+ queryParams.append('startDate', params.startDate);
+ if (params.endDate)
+ queryParams.append('endDate', params.endDate);
+ if (params.productId)
+ queryParams.append('productId', params.productId);
+ if (params.storeId)
+ queryParams.append('storeId', params.storeId);
+ const response = await this.axiosInstance.get(`/products/reviews?${queryParams.toString()}`);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Get reviews count
+ * GET /products/reviews/count
+ */
+ async getReviewsCount(params) {
+ try {
+ const queryParams = new URLSearchParams({
+ altId: params.altId || this.config.locationId,
+ altType: 'location'
+ });
+ if (params.rating)
+ queryParams.append('rating', params.rating.toString());
+ if (params.startDate)
+ queryParams.append('startDate', params.startDate);
+ if (params.endDate)
+ queryParams.append('endDate', params.endDate);
+ if (params.productId)
+ queryParams.append('productId', params.productId);
+ if (params.storeId)
+ queryParams.append('storeId', params.storeId);
+ const response = await this.axiosInstance.get(`/products/reviews/count?${queryParams.toString()}`);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Update a product review
+ * PUT /products/reviews/{reviewId}
+ */
+ async updateProductReview(reviewId, updateData) {
+ try {
+ const payload = {
+ ...updateData,
+ altId: updateData.altId || this.config.locationId,
+ altType: 'location'
+ };
+ const response = await this.axiosInstance.put(`/products/reviews/${reviewId}`, payload);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Delete a product review
+ * DELETE /products/reviews/{reviewId}
+ */
+ async deleteProductReview(reviewId, params) {
+ try {
+ const queryParams = new URLSearchParams({
+ altId: params.altId || this.config.locationId,
+ altType: 'location',
+ productId: params.productId
+ });
+ const response = await this.axiosInstance.delete(`/products/reviews/${reviewId}?${queryParams.toString()}`);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Bulk update product reviews
+ * POST /products/reviews/bulk-update
+ */
+ async bulkUpdateProductReviews(updateData) {
+ try {
+ const payload = {
+ ...updateData,
+ altId: updateData.altId || this.config.locationId,
+ altType: 'location'
+ };
+ const response = await this.axiosInstance.post('/products/reviews/bulk-update', payload);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * PAYMENTS API METHODS
+ */
+ /**
+ * Create white-label integration provider
+ * POST /payments/integrations/provider/whitelabel
+ */
+ async createWhiteLabelIntegrationProvider(data) {
+ try {
+ const response = await this.axiosInstance.post('/payments/integrations/provider/whitelabel', data);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * List white-label integration providers
+ * GET /payments/integrations/provider/whitelabel
+ */
+ async listWhiteLabelIntegrationProviders(params) {
+ try {
+ const queryParams = new URLSearchParams();
+ Object.entries(params).forEach(([key, value]) => {
+ if (value !== undefined && value !== null) {
+ queryParams.append(key, value.toString());
+ }
+ });
+ const response = await this.axiosInstance.get(`/payments/integrations/provider/whitelabel?${queryParams.toString()}`);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * List orders
+ * GET /payments/orders
+ */
+ async listOrders(params) {
+ try {
+ const queryParams = new URLSearchParams();
+ Object.entries(params).forEach(([key, value]) => {
+ if (value !== undefined && value !== null) {
+ queryParams.append(key, value.toString());
+ }
+ });
+ const response = await this.axiosInstance.get(`/payments/orders?${queryParams.toString()}`);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Get order by ID
+ * GET /payments/orders/{orderId}
+ */
+ async getOrderById(orderId, params) {
+ try {
+ const queryParams = new URLSearchParams();
+ Object.entries(params).forEach(([key, value]) => {
+ if (value !== undefined && value !== null && key !== 'orderId') {
+ queryParams.append(key, value.toString());
+ }
+ });
+ const response = await this.axiosInstance.get(`/payments/orders/${orderId}?${queryParams.toString()}`);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Create order fulfillment
+ * POST /payments/orders/{orderId}/fulfillments
+ */
+ async createOrderFulfillment(orderId, data) {
+ try {
+ const response = await this.axiosInstance.post(`/payments/orders/${orderId}/fulfillments`, data);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * List order fulfillments
+ * GET /payments/orders/{orderId}/fulfillments
+ */
+ async listOrderFulfillments(orderId, params) {
+ try {
+ const queryParams = new URLSearchParams();
+ Object.entries(params).forEach(([key, value]) => {
+ if (value !== undefined && value !== null && key !== 'orderId') {
+ queryParams.append(key, value.toString());
+ }
+ });
+ const response = await this.axiosInstance.get(`/payments/orders/${orderId}/fulfillments?${queryParams.toString()}`);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * List transactions
+ * GET /payments/transactions
+ */
+ async listTransactions(params) {
+ try {
+ const queryParams = new URLSearchParams();
+ Object.entries(params).forEach(([key, value]) => {
+ if (value !== undefined && value !== null) {
+ queryParams.append(key, value.toString());
+ }
+ });
+ const response = await this.axiosInstance.get(`/payments/transactions?${queryParams.toString()}`);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Get transaction by ID
+ * GET /payments/transactions/{transactionId}
+ */
+ async getTransactionById(transactionId, params) {
+ try {
+ const queryParams = new URLSearchParams();
+ Object.entries(params).forEach(([key, value]) => {
+ if (value !== undefined && value !== null && key !== 'transactionId') {
+ queryParams.append(key, value.toString());
+ }
+ });
+ const response = await this.axiosInstance.get(`/payments/transactions/${transactionId}?${queryParams.toString()}`);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * List subscriptions
+ * GET /payments/subscriptions
+ */
+ async listSubscriptions(params) {
+ try {
+ const queryParams = new URLSearchParams();
+ Object.entries(params).forEach(([key, value]) => {
+ if (value !== undefined && value !== null) {
+ queryParams.append(key, value.toString());
+ }
+ });
+ const response = await this.axiosInstance.get(`/payments/subscriptions?${queryParams.toString()}`);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Get subscription by ID
+ * GET /payments/subscriptions/{subscriptionId}
+ */
+ async getSubscriptionById(subscriptionId, params) {
+ try {
+ const queryParams = new URLSearchParams();
+ Object.entries(params).forEach(([key, value]) => {
+ if (value !== undefined && value !== null && key !== 'subscriptionId') {
+ queryParams.append(key, value.toString());
+ }
+ });
+ const response = await this.axiosInstance.get(`/payments/subscriptions/${subscriptionId}?${queryParams.toString()}`);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * List coupons
+ * GET /payments/coupon/list
+ */
+ async listCoupons(params) {
+ try {
+ const queryParams = new URLSearchParams();
+ Object.entries(params).forEach(([key, value]) => {
+ if (value !== undefined && value !== null) {
+ queryParams.append(key, value.toString());
+ }
+ });
+ const response = await this.axiosInstance.get(`/payments/coupon/list?${queryParams.toString()}`);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Create coupon
+ * POST /payments/coupon
+ */
+ async createCoupon(data) {
+ try {
+ const response = await this.axiosInstance.post('/payments/coupon', data);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Update coupon
+ * PUT /payments/coupon
+ */
+ async updateCoupon(data) {
+ try {
+ const response = await this.axiosInstance.put('/payments/coupon', data);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Delete coupon
+ * DELETE /payments/coupon
+ */
+ async deleteCoupon(data) {
+ try {
+ const response = await this.axiosInstance.delete('/payments/coupon', { data });
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Get coupon
+ * GET /payments/coupon
+ */
+ async getCoupon(params) {
+ try {
+ const queryParams = new URLSearchParams();
+ Object.entries(params).forEach(([key, value]) => {
+ if (value !== undefined && value !== null) {
+ queryParams.append(key, value.toString());
+ }
+ });
+ const response = await this.axiosInstance.get(`/payments/coupon?${queryParams.toString()}`);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Create custom provider integration
+ * POST /payments/custom-provider/provider
+ */
+ async createCustomProviderIntegration(locationId, data) {
+ try {
+ const queryParams = new URLSearchParams({ locationId });
+ const response = await this.axiosInstance.post(`/payments/custom-provider/provider?${queryParams.toString()}`, data);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Delete custom provider integration
+ * DELETE /payments/custom-provider/provider
+ */
+ async deleteCustomProviderIntegration(locationId) {
+ try {
+ const queryParams = new URLSearchParams({ locationId });
+ const response = await this.axiosInstance.delete(`/payments/custom-provider/provider?${queryParams.toString()}`);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Get custom provider config
+ * GET /payments/custom-provider/connect
+ */
+ async getCustomProviderConfig(locationId) {
+ try {
+ const queryParams = new URLSearchParams({ locationId });
+ const response = await this.axiosInstance.get(`/payments/custom-provider/connect?${queryParams.toString()}`);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Create custom provider config
+ * POST /payments/custom-provider/connect
+ */
+ async createCustomProviderConfig(locationId, data) {
+ try {
+ const queryParams = new URLSearchParams({ locationId });
+ const response = await this.axiosInstance.post(`/payments/custom-provider/connect?${queryParams.toString()}`, data);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Disconnect custom provider config
+ * POST /payments/custom-provider/disconnect
+ */
+ async disconnectCustomProviderConfig(locationId, data) {
+ try {
+ const queryParams = new URLSearchParams({ locationId });
+ const response = await this.axiosInstance.post(`/payments/custom-provider/disconnect?${queryParams.toString()}`, data);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ // =============================================================================
+ // INVOICES API METHODS
+ // =============================================================================
+ /**
+ * Create invoice template
+ * POST /invoices/template
+ */
+ async createInvoiceTemplate(templateData) {
+ try {
+ const payload = {
+ ...templateData,
+ altId: templateData.altId || this.config.locationId,
+ altType: 'location'
+ };
+ const response = await this.axiosInstance.post('/invoices/template', payload);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * List invoice templates
+ * GET /invoices/template
+ */
+ async listInvoiceTemplates(params) {
+ try {
+ const queryParams = {
+ altId: params?.altId || this.config.locationId,
+ altType: 'location',
+ limit: params?.limit || '10',
+ offset: params?.offset || '0',
+ ...(params?.status && { status: params.status }),
+ ...(params?.startAt && { startAt: params.startAt }),
+ ...(params?.endAt && { endAt: params.endAt }),
+ ...(params?.search && { search: params.search }),
+ ...(params?.paymentMode && { paymentMode: params.paymentMode })
+ };
+ const response = await this.axiosInstance.get('/invoices/template', { params: queryParams });
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Get invoice template by ID
+ * GET /invoices/template/{templateId}
+ */
+ async getInvoiceTemplate(templateId, params) {
+ try {
+ const queryParams = {
+ altId: params?.altId || this.config.locationId,
+ altType: 'location'
+ };
+ const response = await this.axiosInstance.get(`/invoices/template/${templateId}`, { params: queryParams });
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Update invoice template
+ * PUT /invoices/template/{templateId}
+ */
+ async updateInvoiceTemplate(templateId, templateData) {
+ try {
+ const payload = {
+ ...templateData,
+ altId: templateData.altId || this.config.locationId,
+ altType: 'location'
+ };
+ const response = await this.axiosInstance.put(`/invoices/template/${templateId}`, payload);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Delete invoice template
+ * DELETE /invoices/template/{templateId}
+ */
+ async deleteInvoiceTemplate(templateId, params) {
+ try {
+ const queryParams = {
+ altId: params?.altId || this.config.locationId,
+ altType: 'location'
+ };
+ const response = await this.axiosInstance.delete(`/invoices/template/${templateId}`, { params: queryParams });
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Update invoice template late fees configuration
+ * PATCH /invoices/template/{templateId}/late-fees-configuration
+ */
+ async updateInvoiceTemplateLateFeesConfiguration(templateId, configData) {
+ try {
+ const payload = {
+ ...configData,
+ altId: configData.altId || this.config.locationId,
+ altType: 'location'
+ };
+ const response = await this.axiosInstance.patch(`/invoices/template/${templateId}/late-fees-configuration`, payload);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Update invoice template payment methods configuration
+ * PATCH /invoices/template/{templateId}/payment-methods-configuration
+ */
+ async updateInvoiceTemplatePaymentMethodsConfiguration(templateId, configData) {
+ try {
+ const payload = {
+ ...configData,
+ altId: configData.altId || this.config.locationId,
+ altType: 'location'
+ };
+ const response = await this.axiosInstance.patch(`/invoices/template/${templateId}/payment-methods-configuration`, payload);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Create invoice schedule
+ * POST /invoices/schedule
+ */
+ async createInvoiceSchedule(scheduleData) {
+ try {
+ const payload = {
+ ...scheduleData,
+ altId: scheduleData.altId || this.config.locationId,
+ altType: 'location'
+ };
+ const response = await this.axiosInstance.post('/invoices/schedule', payload);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * List invoice schedules
+ * GET /invoices/schedule
+ */
+ async listInvoiceSchedules(params) {
+ try {
+ const queryParams = {
+ altId: params?.altId || this.config.locationId,
+ altType: 'location',
+ limit: params?.limit || '10',
+ offset: params?.offset || '0',
+ ...(params?.status && { status: params.status }),
+ ...(params?.startAt && { startAt: params.startAt }),
+ ...(params?.endAt && { endAt: params.endAt }),
+ ...(params?.search && { search: params.search }),
+ ...(params?.paymentMode && { paymentMode: params.paymentMode })
+ };
+ const response = await this.axiosInstance.get('/invoices/schedule', { params: queryParams });
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Get invoice schedule by ID
+ * GET /invoices/schedule/{scheduleId}
+ */
+ async getInvoiceSchedule(scheduleId, params) {
+ try {
+ const queryParams = {
+ altId: params?.altId || this.config.locationId,
+ altType: 'location'
+ };
+ const response = await this.axiosInstance.get(`/invoices/schedule/${scheduleId}`, { params: queryParams });
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Update invoice schedule
+ * PUT /invoices/schedule/{scheduleId}
+ */
+ async updateInvoiceSchedule(scheduleId, scheduleData) {
+ try {
+ const payload = {
+ ...scheduleData,
+ altId: scheduleData.altId || this.config.locationId,
+ altType: 'location'
+ };
+ const response = await this.axiosInstance.put(`/invoices/schedule/${scheduleId}`, payload);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Delete invoice schedule
+ * DELETE /invoices/schedule/{scheduleId}
+ */
+ async deleteInvoiceSchedule(scheduleId, params) {
+ try {
+ const queryParams = {
+ altId: params?.altId || this.config.locationId,
+ altType: 'location'
+ };
+ const response = await this.axiosInstance.delete(`/invoices/schedule/${scheduleId}`, { params: queryParams });
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Update and schedule recurring invoice
+ * POST /invoices/schedule/{scheduleId}/updateAndSchedule
+ */
+ async updateAndScheduleInvoiceSchedule(scheduleId) {
+ try {
+ const response = await this.axiosInstance.post(`/invoices/schedule/${scheduleId}/updateAndSchedule`);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Schedule an invoice schedule
+ * POST /invoices/schedule/{scheduleId}/schedule
+ */
+ async scheduleInvoiceSchedule(scheduleId, scheduleData) {
+ try {
+ const payload = {
+ ...scheduleData,
+ altId: scheduleData.altId || this.config.locationId,
+ altType: 'location'
+ };
+ const response = await this.axiosInstance.post(`/invoices/schedule/${scheduleId}/schedule`, payload);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Manage auto payment for schedule invoice
+ * POST /invoices/schedule/{scheduleId}/auto-payment
+ */
+ async autoPaymentInvoiceSchedule(scheduleId, paymentData) {
+ try {
+ const payload = {
+ ...paymentData,
+ altId: paymentData.altId || this.config.locationId,
+ altType: 'location'
+ };
+ const response = await this.axiosInstance.post(`/invoices/schedule/${scheduleId}/auto-payment`, payload);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Cancel scheduled invoice
+ * POST /invoices/schedule/{scheduleId}/cancel
+ */
+ async cancelInvoiceSchedule(scheduleId, cancelData) {
+ try {
+ const payload = {
+ ...cancelData,
+ altId: cancelData.altId || this.config.locationId,
+ altType: 'location'
+ };
+ const response = await this.axiosInstance.post(`/invoices/schedule/${scheduleId}/cancel`, payload);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Create or update text2pay invoice
+ * POST /invoices/text2pay
+ */
+ async text2PayInvoice(invoiceData) {
+ try {
+ const payload = {
+ ...invoiceData,
+ altId: invoiceData.altId || this.config.locationId,
+ altType: 'location'
+ };
+ const response = await this.axiosInstance.post('/invoices/text2pay', payload);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Generate invoice number
+ * GET /invoices/generate-invoice-number
+ */
+ async generateInvoiceNumber(params) {
+ try {
+ const queryParams = {
+ altId: params?.altId || this.config.locationId,
+ altType: 'location'
+ };
+ const response = await this.axiosInstance.get('/invoices/generate-invoice-number', { params: queryParams });
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Get invoice by ID
+ * GET /invoices/{invoiceId}
+ */
+ async getInvoice(invoiceId, params) {
+ try {
+ const queryParams = {
+ altId: params?.altId || this.config.locationId,
+ altType: 'location'
+ };
+ const response = await this.axiosInstance.get(`/invoices/${invoiceId}`, { params: queryParams });
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Update invoice
+ * PUT /invoices/{invoiceId}
+ */
+ async updateInvoice(invoiceId, invoiceData) {
+ try {
+ const payload = {
+ ...invoiceData,
+ altId: invoiceData.altId || this.config.locationId,
+ altType: 'location'
+ };
+ const response = await this.axiosInstance.put(`/invoices/${invoiceId}`, payload);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Delete invoice
+ * DELETE /invoices/{invoiceId}
+ */
+ async deleteInvoice(invoiceId, params) {
+ try {
+ const queryParams = {
+ altId: params?.altId || this.config.locationId,
+ altType: 'location'
+ };
+ const response = await this.axiosInstance.delete(`/invoices/${invoiceId}`, { params: queryParams });
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Update invoice late fees configuration
+ * PATCH /invoices/{invoiceId}/late-fees-configuration
+ */
+ async updateInvoiceLateFeesConfiguration(invoiceId, configData) {
+ try {
+ const payload = {
+ ...configData,
+ altId: configData.altId || this.config.locationId,
+ altType: 'location'
+ };
+ const response = await this.axiosInstance.patch(`/invoices/${invoiceId}/late-fees-configuration`, payload);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Void invoice
+ * POST /invoices/{invoiceId}/void
+ */
+ async voidInvoice(invoiceId, voidData) {
+ try {
+ const payload = {
+ ...voidData,
+ altId: voidData.altId || this.config.locationId,
+ altType: 'location'
+ };
+ const response = await this.axiosInstance.post(`/invoices/${invoiceId}/void`, payload);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Send invoice
+ * POST /invoices/{invoiceId}/send
+ */
+ async sendInvoice(invoiceId, sendData) {
+ try {
+ const payload = {
+ ...sendData,
+ altId: sendData.altId || this.config.locationId,
+ altType: 'location'
+ };
+ const response = await this.axiosInstance.post(`/invoices/${invoiceId}/send`, payload);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Record manual payment for invoice
+ * POST /invoices/{invoiceId}/record-payment
+ */
+ async recordInvoicePayment(invoiceId, paymentData) {
+ try {
+ const payload = {
+ ...paymentData,
+ altId: paymentData.altId || this.config.locationId,
+ altType: 'location'
+ };
+ const response = await this.axiosInstance.post(`/invoices/${invoiceId}/record-payment`, payload);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Update invoice last visited at
+ * PATCH /invoices/stats/last-visited-at
+ */
+ async updateInvoiceLastVisitedAt(statsData) {
+ try {
+ const response = await this.axiosInstance.patch('/invoices/stats/last-visited-at', statsData);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Create new estimate
+ * POST /invoices/estimate
+ */
+ async createEstimate(estimateData) {
+ try {
+ const payload = {
+ ...estimateData,
+ altId: estimateData.altId || this.config.locationId,
+ altType: 'location'
+ };
+ const response = await this.axiosInstance.post('/invoices/estimate', payload);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Update estimate
+ * PUT /invoices/estimate/{estimateId}
+ */
+ async updateEstimate(estimateId, estimateData) {
+ try {
+ const payload = {
+ ...estimateData,
+ altId: estimateData.altId || this.config.locationId,
+ altType: 'location'
+ };
+ const response = await this.axiosInstance.put(`/invoices/estimate/${estimateId}`, payload);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Delete estimate
+ * DELETE /invoices/estimate/{estimateId}
+ */
+ async deleteEstimate(estimateId, deleteData) {
+ try {
+ const payload = {
+ ...deleteData,
+ altId: deleteData.altId || this.config.locationId,
+ altType: 'location'
+ };
+ const response = await this.axiosInstance.delete(`/invoices/estimate/${estimateId}`, { data: payload });
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Generate estimate number
+ * GET /invoices/estimate/number/generate
+ */
+ async generateEstimateNumber(params) {
+ try {
+ const queryParams = {
+ altId: params?.altId || this.config.locationId,
+ altType: 'location'
+ };
+ const response = await this.axiosInstance.get('/invoices/estimate/number/generate', { params: queryParams });
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Send estimate
+ * POST /invoices/estimate/{estimateId}/send
+ */
+ async sendEstimate(estimateId, sendData) {
+ try {
+ const payload = {
+ ...sendData,
+ altId: sendData.altId || this.config.locationId,
+ altType: 'location'
+ };
+ const response = await this.axiosInstance.post(`/invoices/estimate/${estimateId}/send`, payload);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Create invoice from estimate
+ * POST /invoices/estimate/{estimateId}/invoice
+ */
+ async createInvoiceFromEstimate(estimateId, invoiceData) {
+ try {
+ const payload = {
+ ...invoiceData,
+ altId: invoiceData.altId || this.config.locationId,
+ altType: 'location'
+ };
+ const response = await this.axiosInstance.post(`/invoices/estimate/${estimateId}/invoice`, payload);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * List estimates
+ * GET /invoices/estimate/list
+ */
+ async listEstimates(params) {
+ try {
+ const queryParams = {
+ altId: params?.altId || this.config.locationId,
+ altType: 'location',
+ limit: params?.limit || '10',
+ offset: params?.offset || '0',
+ ...(params?.startAt && { startAt: params.startAt }),
+ ...(params?.endAt && { endAt: params.endAt }),
+ ...(params?.search && { search: params.search }),
+ ...(params?.status && { status: params.status }),
+ ...(params?.contactId && { contactId: params.contactId })
+ };
+ const response = await this.axiosInstance.get('/invoices/estimate/list', { params: queryParams });
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Update estimate last visited at
+ * PATCH /invoices/estimate/stats/last-visited-at
+ */
+ async updateEstimateLastVisitedAt(statsData) {
+ try {
+ const response = await this.axiosInstance.patch('/invoices/estimate/stats/last-visited-at', statsData);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * List estimate templates
+ * GET /invoices/estimate/template
+ */
+ async listEstimateTemplates(params) {
+ try {
+ const queryParams = {
+ altId: params?.altId || this.config.locationId,
+ altType: 'location',
+ limit: params?.limit || '10',
+ offset: params?.offset || '0',
+ ...(params?.search && { search: params.search })
+ };
+ const response = await this.axiosInstance.get('/invoices/estimate/template', { params: queryParams });
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Create estimate template
+ * POST /invoices/estimate/template
+ */
+ async createEstimateTemplate(templateData) {
+ try {
+ const payload = {
+ ...templateData,
+ altId: templateData.altId || this.config.locationId,
+ altType: 'location'
+ };
+ const response = await this.axiosInstance.post('/invoices/estimate/template', payload);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Update estimate template
+ * PUT /invoices/estimate/template/{templateId}
+ */
+ async updateEstimateTemplate(templateId, templateData) {
+ try {
+ const payload = {
+ ...templateData,
+ altId: templateData.altId || this.config.locationId,
+ altType: 'location'
+ };
+ const response = await this.axiosInstance.put(`/invoices/estimate/template/${templateId}`, payload);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Delete estimate template
+ * DELETE /invoices/estimate/template/{templateId}
+ */
+ async deleteEstimateTemplate(templateId, deleteData) {
+ try {
+ const payload = {
+ ...deleteData,
+ altId: deleteData.altId || this.config.locationId,
+ altType: 'location'
+ };
+ const response = await this.axiosInstance.delete(`/invoices/estimate/template/${templateId}`, { data: payload });
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Preview estimate template
+ * GET /invoices/estimate/template/preview
+ */
+ async previewEstimateTemplate(params) {
+ try {
+ const queryParams = {
+ altId: params?.altId || this.config.locationId,
+ altType: 'location',
+ templateId: params?.templateId || ''
+ };
+ const response = await this.axiosInstance.get('/invoices/estimate/template/preview', { params: queryParams });
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * Create invoice
+ * POST /invoices/
+ */
+ async createInvoice(invoiceData) {
+ try {
+ const payload = {
+ ...invoiceData,
+ altId: invoiceData.altId || this.config.locationId,
+ altType: 'location'
+ };
+ const response = await this.axiosInstance.post('/invoices/', payload);
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+ /**
+ * List invoices
+ * GET /invoices/
+ */
+ async listInvoices(params) {
+ try {
+ const queryParams = {
+ altId: params?.altId || this.config.locationId,
+ altType: 'location',
+ limit: params?.limit || '10',
+ offset: params?.offset || '0',
+ ...(params?.status && { status: params.status }),
+ ...(params?.startAt && { startAt: params.startAt }),
+ ...(params?.endAt && { endAt: params.endAt }),
+ ...(params?.search && { search: params.search }),
+ ...(params?.paymentMode && { paymentMode: params.paymentMode }),
+ ...(params?.contactId && { contactId: params.contactId }),
+ ...(params?.sortField && { sortField: params.sortField }),
+ ...(params?.sortOrder && { sortOrder: params.sortOrder })
+ };
+ const response = await this.axiosInstance.get('/invoices/', { params: queryParams });
+ return this.wrapResponse(response.data);
+ }
+ catch (error) {
+ throw error;
+ }
+ }
+}
diff --git a/mcp-diagrams/ghl-mcp-apps-only/dist/server.js b/mcp-diagrams/ghl-mcp-apps-only/dist/server.js
new file mode 100644
index 0000000..87611bd
--- /dev/null
+++ b/mcp-diagrams/ghl-mcp-apps-only/dist/server.js
@@ -0,0 +1,139 @@
+/**
+ * GoHighLevel MCP Apps Server (Slimmed Down)
+ * Only includes MCP Apps - no other tools
+ */
+import { Server } from '@modelcontextprotocol/sdk/server/index.js';
+import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
+import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, McpError } from '@modelcontextprotocol/sdk/types.js';
+import * as dotenv from 'dotenv';
+import { GHLApiClient } from './clients/ghl-api-client.js';
+import { MCPAppsManager } from './apps/index.js';
+// Load environment variables
+dotenv.config();
+/**
+ * MCP Apps Only Server
+ */
+class GHLMCPAppsServer {
+ server;
+ ghlClient;
+ mcpAppsManager;
+ constructor() {
+ // Initialize MCP server with capabilities
+ this.server = new Server({
+ name: 'ghl-mcp-apps-only',
+ version: '1.0.0',
+ }, {
+ capabilities: {
+ tools: {},
+ resources: {},
+ },
+ });
+ // Initialize GHL API client
+ this.ghlClient = this.initializeGHLClient();
+ // Initialize MCP Apps Manager
+ this.mcpAppsManager = new MCPAppsManager(this.ghlClient);
+ this.setupHandlers();
+ this.setupErrorHandling();
+ }
+ /**
+ * Initialize the GHL API client with config from environment
+ */
+ initializeGHLClient() {
+ const config = {
+ accessToken: process.env.GHL_API_KEY || '',
+ locationId: process.env.GHL_LOCATION_ID || '',
+ baseUrl: process.env.GHL_BASE_URL || 'https://services.leadconnectorhq.com',
+ version: '2021-07-28'
+ };
+ if (!config.accessToken) {
+ process.stderr.write('Warning: GHL_API_KEY not set in environment\n');
+ }
+ if (!config.locationId) {
+ process.stderr.write('Warning: GHL_LOCATION_ID not set in environment\n');
+ }
+ return new GHLApiClient(config);
+ }
+ /**
+ * Setup request handlers
+ */
+ setupHandlers() {
+ // List tools - only MCP App tools
+ this.server.setRequestHandler(ListToolsRequestSchema, async () => {
+ const appTools = this.mcpAppsManager.getToolDefinitions();
+ process.stderr.write(`[MCP Apps Only] Listing ${appTools.length} app tools\n`);
+ return { tools: appTools };
+ });
+ // List resources - MCP App UI resources
+ this.server.setRequestHandler(ListResourcesRequestSchema, async () => {
+ const resourceUris = this.mcpAppsManager.getResourceURIs();
+ const resources = resourceUris.map(uri => {
+ const handler = this.mcpAppsManager.getResourceHandler(uri);
+ return {
+ uri: uri,
+ name: uri,
+ mimeType: handler?.mimeType || 'text/html;profile=mcp-app'
+ };
+ });
+ process.stderr.write(`[MCP Apps Only] Listing ${resources.length} UI resources\n`);
+ return { resources };
+ });
+ // Read resource - serve UI HTML
+ this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
+ const uri = request.params.uri;
+ process.stderr.write(`[MCP Apps Only] Reading resource: ${uri}\n`);
+ const handler = this.mcpAppsManager.getResourceHandler(uri);
+ if (!handler) {
+ throw new McpError(ErrorCode.InvalidRequest, `Resource not found: ${uri}`);
+ }
+ return {
+ contents: [{
+ uri: uri,
+ mimeType: handler.mimeType,
+ text: handler.getContent()
+ }]
+ };
+ });
+ // Call tool - execute MCP App tools
+ this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
+ const { name, arguments: args } = request.params;
+ process.stderr.write(`[MCP Apps Only] Calling tool: ${name}\n`);
+ if (!this.mcpAppsManager.isAppTool(name)) {
+ throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
+ }
+ try {
+ const result = await this.mcpAppsManager.executeTool(name, args || {});
+ return result;
+ }
+ catch (error) {
+ process.stderr.write(`[MCP Apps Only] Tool error: ${error.message}\n`);
+ throw new McpError(ErrorCode.InternalError, error.message);
+ }
+ });
+ }
+ /**
+ * Setup error handling
+ */
+ setupErrorHandling() {
+ this.server.onerror = (error) => {
+ process.stderr.write(`[MCP Apps Only] Server error: ${error}\n`);
+ };
+ process.on('SIGINT', async () => {
+ await this.server.close();
+ process.exit(0);
+ });
+ }
+ /**
+ * Start the server
+ */
+ async run() {
+ const transport = new StdioServerTransport();
+ await this.server.connect(transport);
+ process.stderr.write('[MCP Apps Only] Server started - Apps only mode\n');
+ }
+}
+// Start server
+const server = new GHLMCPAppsServer();
+server.run().catch((error) => {
+ process.stderr.write(`Failed to start server: ${error}\n`);
+ process.exit(1);
+});
diff --git a/mcp-diagrams/ghl-mcp-apps-only/dist/types/ghl-types.js b/mcp-diagrams/ghl-mcp-apps-only/dist/types/ghl-types.js
new file mode 100644
index 0000000..8fdf1f5
--- /dev/null
+++ b/mcp-diagrams/ghl-mcp-apps-only/dist/types/ghl-types.js
@@ -0,0 +1,5 @@
+/**
+ * TypeScript interfaces for GoHighLevel API integration
+ * Based on official OpenAPI specifications v2021-07-28 (Contacts) and v2021-04-15 (Conversations)
+ */
+export {};
diff --git a/mcp-diagrams/ghl-mcp-apps-only/package.json b/mcp-diagrams/ghl-mcp-apps-only/package.json
new file mode 100644
index 0000000..67223fe
--- /dev/null
+++ b/mcp-diagrams/ghl-mcp-apps-only/package.json
@@ -0,0 +1,22 @@
+{
+ "name": "ghl-mcp-apps-only",
+ "version": "1.0.0",
+ "description": "GoHighLevel MCP Apps Only - Slimmed down for testing",
+ "main": "dist/server.js",
+ "type": "module",
+ "scripts": {
+ "build": "tsc",
+ "start": "node dist/server.js",
+ "dev": "tsx src/server.ts"
+ },
+ "dependencies": {
+ "@modelcontextprotocol/sdk": "^1.12.1",
+ "axios": "^1.9.0",
+ "dotenv": "^16.5.0"
+ },
+ "devDependencies": {
+ "@types/node": "^22.15.29",
+ "tsx": "^4.7.0",
+ "typescript": "^5.8.3"
+ }
+}
diff --git a/mcp-diagrams/ghl-mcp-apps-only/src/apps/index.ts b/mcp-diagrams/ghl-mcp-apps-only/src/apps/index.ts
new file mode 100644
index 0000000..7184538
--- /dev/null
+++ b/mcp-diagrams/ghl-mcp-apps-only/src/apps/index.ts
@@ -0,0 +1,824 @@
+/**
+ * MCP Apps Manager
+ * Manages rich UI components for GoHighLevel MCP Server
+ */
+
+import { Tool } from '@modelcontextprotocol/sdk/types.js';
+import { GHLApiClient } from '../clients/ghl-api-client.js';
+import * as fs from 'fs';
+import * as path from 'path';
+import { fileURLToPath } from 'url';
+
+export interface AppToolResult {
+ content: Array<{ type: 'text'; text: string }>;
+ structuredContent?: Record;
+ [key: string]: unknown;
+}
+
+export interface AppResourceHandler {
+ uri: string;
+ mimeType: string;
+ getContent: () => string;
+}
+
+/**
+ * MCP Apps Manager class
+ * Registers app tools and handles structuredContent responses
+ */
+// ESM equivalent of __dirname
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = path.dirname(__filename);
+
+// Resolve UI build path - works regardless of working directory
+function getUIBuildPath(): string {
+ // When compiled, this file is at dist/apps/index.js
+ // UI files are at dist/app-ui/
+ const fromDist = path.resolve(__dirname, '..', 'app-ui');
+ if (fs.existsSync(fromDist)) {
+ return fromDist;
+ }
+ // Fallback: try process.cwd() based paths
+ const appUiPath = path.join(process.cwd(), 'dist', 'app-ui');
+ if (fs.existsSync(appUiPath)) {
+ return appUiPath;
+ }
+ // Default fallback
+ return fromDist;
+}
+
+export class MCPAppsManager {
+ private ghlClient: GHLApiClient;
+ private resourceHandlers: Map = new Map();
+ private uiBuildPath: string;
+
+ constructor(ghlClient: GHLApiClient) {
+ this.ghlClient = ghlClient;
+ this.uiBuildPath = getUIBuildPath();
+ process.stderr.write(`[MCP Apps] UI build path: ${this.uiBuildPath}\n`);
+ this.registerResourceHandlers();
+ }
+
+ /**
+ * Register all UI resource handlers
+ */
+ private registerResourceHandlers(): void {
+ const resources: Array<{ uri: string; file: string }> = [
+ // All 11 MCP Apps
+ { uri: 'ui://ghl/mcp-app', file: 'mcp-app.html' },
+ { uri: 'ui://ghl/pipeline-board', file: 'pipeline-board.html' },
+ { uri: 'ui://ghl/quick-book', file: 'quick-book.html' },
+ { uri: 'ui://ghl/opportunity-card', file: 'opportunity-card.html' },
+ { uri: 'ui://ghl/contact-grid', file: 'contact-grid.html' },
+ { uri: 'ui://ghl/calendar-view', file: 'calendar-view.html' },
+ { uri: 'ui://ghl/invoice-preview', file: 'invoice-preview.html' },
+ { uri: 'ui://ghl/campaign-stats', file: 'campaign-stats.html' },
+ { uri: 'ui://ghl/agent-stats', file: 'agent-stats.html' },
+ { uri: 'ui://ghl/contact-timeline', file: 'contact-timeline.html' },
+ { uri: 'ui://ghl/workflow-status', file: 'workflow-status.html' },
+ ];
+
+ for (const resource of resources) {
+ this.resourceHandlers.set(resource.uri, {
+ uri: resource.uri,
+ mimeType: 'text/html;profile=mcp-app',
+ getContent: () => this.loadUIResource(resource.file),
+ });
+ }
+ }
+
+ /**
+ * Load UI resource from build directory
+ */
+ private loadUIResource(filename: string): string {
+ const filePath = path.join(this.uiBuildPath, filename);
+ try {
+ return fs.readFileSync(filePath, 'utf-8');
+ } catch (error) {
+ process.stderr.write(`[MCP Apps] UI resource not found: ${filePath}\n`);
+ return this.getFallbackHTML(filename);
+ }
+ }
+
+ /**
+ * Generate fallback HTML when UI resource is not built
+ */
+ private getFallbackHTML(filename: string): string {
+ const componentName = filename.replace('.html', '');
+ return `
+
+
+
+
+
+ GHL ${componentName}
+
+
+
+
+
UI component "${componentName}" is loading...
+
Run npm run build:ui to build UI components.
+
+
+
+
+ `.trim();
+ }
+
+ /**
+ * Get tool definitions for all app tools
+ */
+ getToolDefinitions(): Tool[] {
+ return [
+ // 1. Contact Grid - search and display contacts
+ {
+ name: 'view_contact_grid',
+ description: 'Display contact search results in a data grid with sorting and pagination. Returns a visual UI component.',
+ inputSchema: {
+ type: 'object',
+ properties: {
+ query: { type: 'string', description: 'Search query string' },
+ limit: { type: 'number', description: 'Maximum results (default: 25)' }
+ }
+ },
+ _meta: {
+ ui: { resourceUri: 'ui://ghl/contact-grid' }
+ }
+ },
+ // 2. Pipeline Board - Kanban view of opportunities
+ {
+ name: 'view_pipeline_board',
+ description: 'Display a pipeline as an interactive Kanban board with opportunities. Returns a visual UI component.',
+ inputSchema: {
+ type: 'object',
+ properties: {
+ pipelineId: { type: 'string', description: 'Pipeline ID to display' }
+ },
+ required: ['pipelineId']
+ },
+ _meta: {
+ ui: { resourceUri: 'ui://ghl/pipeline-board' }
+ }
+ },
+ // 3. Quick Book - appointment booking
+ {
+ name: 'view_quick_book',
+ description: 'Display a quick booking interface for scheduling appointments. Returns a visual UI component.',
+ inputSchema: {
+ type: 'object',
+ properties: {
+ calendarId: { type: 'string', description: 'Calendar ID for booking' },
+ contactId: { type: 'string', description: 'Optional contact ID to pre-fill' }
+ },
+ required: ['calendarId']
+ },
+ _meta: {
+ ui: { resourceUri: 'ui://ghl/quick-book' }
+ }
+ },
+ // 4. Opportunity Card - single opportunity details
+ {
+ name: 'view_opportunity_card',
+ description: 'Display a single opportunity with details, value, and stage info. Returns a visual UI component.',
+ inputSchema: {
+ type: 'object',
+ properties: {
+ opportunityId: { type: 'string', description: 'Opportunity ID to display' }
+ },
+ required: ['opportunityId']
+ },
+ _meta: {
+ ui: { resourceUri: 'ui://ghl/opportunity-card' }
+ }
+ },
+ // 5. Calendar View - calendar with events
+ {
+ name: 'view_calendar',
+ description: 'Display a calendar with events and appointments. Returns a visual UI component.',
+ inputSchema: {
+ type: 'object',
+ properties: {
+ calendarId: { type: 'string', description: 'Calendar ID to display' },
+ startDate: { type: 'string', description: 'Start date (ISO format)' },
+ endDate: { type: 'string', description: 'End date (ISO format)' }
+ },
+ required: ['calendarId']
+ },
+ _meta: {
+ ui: { resourceUri: 'ui://ghl/calendar-view' }
+ }
+ },
+ // 6. Invoice Preview - invoice details
+ {
+ name: 'view_invoice',
+ description: 'Display an invoice preview with line items and payment status. Returns a visual UI component.',
+ inputSchema: {
+ type: 'object',
+ properties: {
+ invoiceId: { type: 'string', description: 'Invoice ID to display' }
+ },
+ required: ['invoiceId']
+ },
+ _meta: {
+ ui: { resourceUri: 'ui://ghl/invoice-preview' }
+ }
+ },
+ // 7. Campaign Stats - campaign performance metrics
+ {
+ name: 'view_campaign_stats',
+ description: 'Display campaign statistics and performance metrics. Returns a visual UI component.',
+ inputSchema: {
+ type: 'object',
+ properties: {
+ campaignId: { type: 'string', description: 'Campaign ID to display stats for' }
+ },
+ required: ['campaignId']
+ },
+ _meta: {
+ ui: { resourceUri: 'ui://ghl/campaign-stats' }
+ }
+ },
+ // 8. Agent Stats - agent/user performance
+ {
+ name: 'view_agent_stats',
+ description: 'Display agent/user performance statistics and metrics. Returns a visual UI component.',
+ inputSchema: {
+ type: 'object',
+ properties: {
+ userId: { type: 'string', description: 'User/Agent ID to display stats for' },
+ dateRange: { type: 'string', description: 'Date range (e.g., "last7days", "last30days")' }
+ }
+ },
+ _meta: {
+ ui: { resourceUri: 'ui://ghl/agent-stats' }
+ }
+ },
+ // 9. Contact Timeline - activity history for a contact
+ {
+ name: 'view_contact_timeline',
+ description: 'Display a contact\'s activity timeline with all interactions. Returns a visual UI component.',
+ inputSchema: {
+ type: 'object',
+ properties: {
+ contactId: { type: 'string', description: 'Contact ID to display timeline for' }
+ },
+ required: ['contactId']
+ },
+ _meta: {
+ ui: { resourceUri: 'ui://ghl/contact-timeline' }
+ }
+ },
+ // 10. Workflow Status - workflow execution status
+ {
+ name: 'view_workflow_status',
+ description: 'Display workflow execution status and history. Returns a visual UI component.',
+ inputSchema: {
+ type: 'object',
+ properties: {
+ workflowId: { type: 'string', description: 'Workflow ID to display status for' }
+ },
+ required: ['workflowId']
+ },
+ _meta: {
+ ui: { resourceUri: 'ui://ghl/workflow-status' }
+ }
+ },
+ // 11. MCP App - generic/main dashboard
+ {
+ name: 'view_dashboard',
+ description: 'Display the main GHL dashboard overview. Returns a visual UI component.',
+ inputSchema: {
+ type: 'object',
+ properties: {}
+ },
+ _meta: {
+ ui: { resourceUri: 'ui://ghl/mcp-app' }
+ }
+ },
+ // 12. Update Opportunity - action tool for UI to update opportunities
+ {
+ name: 'update_opportunity',
+ description: 'Update an opportunity (move to stage, change value, status, etc.)',
+ inputSchema: {
+ type: 'object',
+ properties: {
+ opportunityId: { type: 'string', description: 'Opportunity ID to update' },
+ pipelineStageId: { type: 'string', description: 'New stage ID (for moving)' },
+ name: { type: 'string', description: 'Opportunity name' },
+ monetaryValue: { type: 'number', description: 'Monetary value' },
+ status: { type: 'string', enum: ['open', 'won', 'lost', 'abandoned'], description: 'Opportunity status' }
+ },
+ required: ['opportunityId']
+ }
+ }
+ ];
+ }
+
+ /**
+ * Get app tool names for routing
+ */
+ getAppToolNames(): string[] {
+ return [
+ 'view_contact_grid',
+ 'view_pipeline_board',
+ 'view_quick_book',
+ 'view_opportunity_card',
+ 'view_calendar',
+ 'view_invoice',
+ 'view_campaign_stats',
+ 'view_agent_stats',
+ 'view_contact_timeline',
+ 'view_workflow_status',
+ 'view_dashboard',
+ 'update_opportunity'
+ ];
+ }
+
+ /**
+ * Check if a tool is an app tool
+ */
+ isAppTool(toolName: string): boolean {
+ return this.getAppToolNames().includes(toolName);
+ }
+
+ /**
+ * Execute an app tool
+ */
+ async executeTool(toolName: string, args: Record): Promise {
+ process.stderr.write(`[MCP Apps] Executing app tool: ${toolName}\n`);
+
+ switch (toolName) {
+ case 'view_contact_grid':
+ return await this.viewContactGrid(args.query, args.limit);
+ case 'view_pipeline_board':
+ return await this.viewPipelineBoard(args.pipelineId);
+ case 'view_quick_book':
+ return await this.viewQuickBook(args.calendarId, args.contactId);
+ case 'view_opportunity_card':
+ return await this.viewOpportunityCard(args.opportunityId);
+ case 'view_calendar':
+ return await this.viewCalendar(args.calendarId, args.startDate, args.endDate);
+ case 'view_invoice':
+ return await this.viewInvoice(args.invoiceId);
+ case 'view_campaign_stats':
+ return await this.viewCampaignStats(args.campaignId);
+ case 'view_agent_stats':
+ return await this.viewAgentStats(args.userId, args.dateRange);
+ case 'view_contact_timeline':
+ return await this.viewContactTimeline(args.contactId);
+ case 'view_workflow_status':
+ return await this.viewWorkflowStatus(args.workflowId);
+ case 'view_dashboard':
+ return await this.viewDashboard();
+ case 'update_opportunity':
+ return await this.updateOpportunity(args as {
+ opportunityId: string;
+ pipelineStageId?: string;
+ name?: string;
+ monetaryValue?: number;
+ status?: 'open' | 'won' | 'lost' | 'abandoned';
+ });
+ default:
+ throw new Error(`Unknown app tool: ${toolName}`);
+ }
+ }
+
+ /**
+ * View contact grid (search results)
+ */
+ private async viewContactGrid(query?: string, limit?: number): Promise {
+ const response = await this.ghlClient.searchContacts({
+ locationId: this.ghlClient.getConfig().locationId,
+ query: query,
+ limit: limit || 25
+ });
+
+ if (!response.success) {
+ throw new Error(response.error?.message || 'Failed to search contacts');
+ }
+
+ const data = response.data;
+ const resourceHandler = this.resourceHandlers.get('ui://ghl/contact-grid')!;
+
+ return this.createAppResult(
+ `Found ${data?.contacts?.length || 0} contacts`,
+ resourceHandler.uri,
+ resourceHandler.mimeType,
+ resourceHandler.getContent(),
+ data
+ );
+ }
+
+ /**
+ * View pipeline board (Kanban)
+ */
+ private async viewPipelineBoard(pipelineId: string): Promise {
+ const [pipelinesResponse, opportunitiesResponse] = await Promise.all([
+ this.ghlClient.getPipelines(),
+ this.ghlClient.searchOpportunities({
+ location_id: this.ghlClient.getConfig().locationId,
+ pipeline_id: pipelineId
+ })
+ ]);
+
+ if (!pipelinesResponse.success) {
+ throw new Error(pipelinesResponse.error?.message || 'Failed to get pipeline');
+ }
+
+ const pipeline = pipelinesResponse.data?.pipelines?.find((p: any) => p.id === pipelineId);
+ const opportunities = opportunitiesResponse.data?.opportunities || [];
+
+ // Simplify opportunity data to only include fields the UI needs (reduces payload size)
+ const simplifiedOpportunities = opportunities.map((opp: any) => ({
+ id: opp.id,
+ name: opp.name || 'Untitled',
+ pipelineStageId: opp.pipelineStageId,
+ status: opp.status || 'open',
+ monetaryValue: opp.monetaryValue || 0,
+ contact: opp.contact ? {
+ name: opp.contact.name || 'Unknown',
+ email: opp.contact.email,
+ phone: opp.contact.phone
+ } : { name: 'Unknown' },
+ updatedAt: opp.updatedAt || opp.createdAt,
+ createdAt: opp.createdAt,
+ source: opp.source
+ }));
+
+ const data = {
+ pipeline,
+ opportunities: simplifiedOpportunities,
+ stages: pipeline?.stages || []
+ };
+
+ const resourceHandler = this.resourceHandlers.get('ui://ghl/pipeline-board')!;
+
+ return this.createAppResult(
+ `Pipeline: ${pipeline?.name || 'Unknown'} (${opportunities.length} opportunities)`,
+ resourceHandler.uri,
+ resourceHandler.mimeType,
+ resourceHandler.getContent(),
+ data
+ );
+ }
+
+ /**
+ * View quick book interface
+ */
+ private async viewQuickBook(calendarId: string, contactId?: string): Promise {
+ const [calendarResponse, contactResponse] = await Promise.all([
+ this.ghlClient.getCalendar(calendarId),
+ contactId ? this.ghlClient.getContact(contactId) : Promise.resolve({ success: true, data: null })
+ ]);
+
+ if (!calendarResponse.success) {
+ throw new Error(calendarResponse.error?.message || 'Failed to get calendar');
+ }
+
+ const data = {
+ calendar: calendarResponse.data,
+ contact: contactResponse.data,
+ locationId: this.ghlClient.getConfig().locationId
+ };
+
+ const resourceHandler = this.resourceHandlers.get('ui://ghl/quick-book')!;
+
+ return this.createAppResult(
+ `Quick booking for calendar: ${(calendarResponse.data as any)?.name || calendarId}`,
+ resourceHandler.uri,
+ resourceHandler.mimeType,
+ resourceHandler.getContent(),
+ data
+ );
+ }
+
+ /**
+ * View opportunity card
+ */
+ private async viewOpportunityCard(opportunityId: string): Promise {
+ const response = await this.ghlClient.getOpportunity(opportunityId);
+
+ if (!response.success) {
+ throw new Error(response.error?.message || 'Failed to get opportunity');
+ }
+
+ const opportunity = response.data;
+ const resourceHandler = this.resourceHandlers.get('ui://ghl/opportunity-card')!;
+
+ return this.createAppResult(
+ `Opportunity: ${(opportunity as any)?.name || opportunityId}`,
+ resourceHandler.uri,
+ resourceHandler.mimeType,
+ resourceHandler.getContent(),
+ opportunity
+ );
+ }
+
+ /**
+ * View calendar
+ */
+ private async viewCalendar(calendarId: string, startDate?: string, endDate?: string): Promise {
+ const now = new Date();
+ const start = startDate || new Date(now.getFullYear(), now.getMonth(), 1).toISOString();
+ const end = endDate || new Date(now.getFullYear(), now.getMonth() + 1, 0).toISOString();
+
+ const [calendarResponse, eventsResponse] = await Promise.all([
+ this.ghlClient.getCalendar(calendarId),
+ this.ghlClient.getCalendarEvents({
+ calendarId: calendarId,
+ startTime: start,
+ endTime: end,
+ locationId: this.ghlClient.getConfig().locationId
+ })
+ ]);
+
+ if (!calendarResponse.success) {
+ throw new Error(calendarResponse.error?.message || 'Failed to get calendar');
+ }
+
+ const calendar = calendarResponse.data as any;
+ const data = {
+ calendar: calendarResponse.data,
+ events: eventsResponse.data?.events || [],
+ startDate: start,
+ endDate: end
+ };
+
+ const resourceHandler = this.resourceHandlers.get('ui://ghl/calendar-view')!;
+
+ return this.createAppResult(
+ `Calendar: ${calendar?.name || 'Unknown'} (${data.events.length} events)`,
+ resourceHandler.uri,
+ resourceHandler.mimeType,
+ resourceHandler.getContent(),
+ data
+ );
+ }
+
+ /**
+ * View campaign stats
+ */
+ private async viewCampaignStats(campaignId: string): Promise {
+ // Get email campaigns
+ const response = await this.ghlClient.getEmailCampaigns({});
+
+ const campaigns = response.data?.schedules || [];
+ const campaign = campaigns.find((c: any) => c.id === campaignId) || { id: campaignId };
+
+ const data = {
+ campaign,
+ campaigns,
+ campaignId,
+ locationId: this.ghlClient.getConfig().locationId
+ };
+
+ const resourceHandler = this.resourceHandlers.get('ui://ghl/campaign-stats')!;
+
+ return this.createAppResult(
+ `Campaign stats: ${(campaign as any)?.name || campaignId}`,
+ resourceHandler.uri,
+ resourceHandler.mimeType,
+ resourceHandler.getContent(),
+ data
+ );
+ }
+
+ /**
+ * View agent stats
+ */
+ private async viewAgentStats(userId?: string, dateRange?: string): Promise {
+ // Get location info which may include user data
+ const locationResponse = await this.ghlClient.getLocationById(this.ghlClient.getConfig().locationId);
+
+ const data = {
+ userId,
+ dateRange: dateRange || 'last30days',
+ location: locationResponse.data,
+ locationId: this.ghlClient.getConfig().locationId
+ };
+
+ const resourceHandler = this.resourceHandlers.get('ui://ghl/agent-stats')!;
+
+ return this.createAppResult(
+ userId ? `Agent stats: ${userId}` : 'Agent overview',
+ resourceHandler.uri,
+ resourceHandler.mimeType,
+ resourceHandler.getContent(),
+ data
+ );
+ }
+
+ /**
+ * View contact timeline
+ */
+ private async viewContactTimeline(contactId: string): Promise {
+ const [contactResponse, notesResponse, tasksResponse] = await Promise.all([
+ this.ghlClient.getContact(contactId),
+ this.ghlClient.getContactNotes(contactId),
+ this.ghlClient.getContactTasks(contactId)
+ ]);
+
+ if (!contactResponse.success) {
+ throw new Error(contactResponse.error?.message || 'Failed to get contact');
+ }
+
+ const contact = contactResponse.data as any;
+ const data = {
+ contact: contactResponse.data,
+ notes: notesResponse.data || [],
+ tasks: tasksResponse.data || []
+ };
+
+ const resourceHandler = this.resourceHandlers.get('ui://ghl/contact-timeline')!;
+
+ return this.createAppResult(
+ `Timeline for ${contact?.firstName || ''} ${contact?.lastName || ''}`,
+ resourceHandler.uri,
+ resourceHandler.mimeType,
+ resourceHandler.getContent(),
+ data
+ );
+ }
+
+ /**
+ * View workflow status
+ */
+ private async viewWorkflowStatus(workflowId: string): Promise {
+ const response = await this.ghlClient.getWorkflows({
+ locationId: this.ghlClient.getConfig().locationId
+ });
+
+ const workflows = response.data?.workflows || [];
+ const workflow = workflows.find((w: any) => w.id === workflowId) || { id: workflowId };
+
+ const data = {
+ workflow,
+ workflows,
+ workflowId,
+ locationId: this.ghlClient.getConfig().locationId
+ };
+
+ const resourceHandler = this.resourceHandlers.get('ui://ghl/workflow-status')!;
+
+ return this.createAppResult(
+ `Workflow: ${(workflow as any)?.name || workflowId}`,
+ resourceHandler.uri,
+ resourceHandler.mimeType,
+ resourceHandler.getContent(),
+ data
+ );
+ }
+
+ /**
+ * View main dashboard
+ */
+ private async viewDashboard(): Promise {
+ const [contactsResponse, pipelinesResponse, calendarsResponse] = await Promise.all([
+ this.ghlClient.searchContacts({ locationId: this.ghlClient.getConfig().locationId, limit: 10 }),
+ this.ghlClient.getPipelines(),
+ this.ghlClient.getCalendars()
+ ]);
+
+ const data = {
+ recentContacts: contactsResponse.data?.contacts || [],
+ pipelines: pipelinesResponse.data?.pipelines || [],
+ calendars: calendarsResponse.data?.calendars || [],
+ locationId: this.ghlClient.getConfig().locationId
+ };
+
+ const resourceHandler = this.resourceHandlers.get('ui://ghl/mcp-app')!;
+
+ return this.createAppResult(
+ 'GHL Dashboard Overview',
+ resourceHandler.uri,
+ resourceHandler.mimeType,
+ resourceHandler.getContent(),
+ data
+ );
+ }
+
+ /**
+ * View invoice
+ */
+ private async viewInvoice(invoiceId: string): Promise {
+ const response = await this.ghlClient.getInvoice(invoiceId, {
+ altId: this.ghlClient.getConfig().locationId,
+ altType: 'location'
+ });
+
+ if (!response.success) {
+ throw new Error(response.error?.message || 'Failed to get invoice');
+ }
+
+ const invoice = response.data;
+ const resourceHandler = this.resourceHandlers.get('ui://ghl/invoice-preview')!;
+
+ return this.createAppResult(
+ `Invoice #${invoice?.invoiceNumber || invoiceId} - ${invoice?.status || 'Unknown status'}`,
+ resourceHandler.uri,
+ resourceHandler.mimeType,
+ resourceHandler.getContent(),
+ invoice
+ );
+ }
+
+ /**
+ * Update opportunity (action tool for UI)
+ */
+ private async updateOpportunity(args: {
+ opportunityId: string;
+ pipelineStageId?: string;
+ name?: string;
+ monetaryValue?: number;
+ status?: 'open' | 'won' | 'lost' | 'abandoned';
+ }): Promise {
+ const { opportunityId, ...updates } = args;
+
+ // Build the update payload
+ const updatePayload: any = {};
+ if (updates.pipelineStageId) updatePayload.pipelineStageId = updates.pipelineStageId;
+ if (updates.name) updatePayload.name = updates.name;
+ if (updates.monetaryValue !== undefined) updatePayload.monetaryValue = updates.monetaryValue;
+ if (updates.status) updatePayload.status = updates.status;
+
+ process.stderr.write(`[MCP Apps] Updating opportunity ${opportunityId}: ${JSON.stringify(updatePayload)}\n`);
+
+ const response = await this.ghlClient.updateOpportunity(opportunityId, updatePayload);
+
+ if (!response.success) {
+ throw new Error(response.error?.message || 'Failed to update opportunity');
+ }
+
+ const opportunity = response.data;
+
+ return {
+ content: [{ type: 'text', text: `Updated opportunity: ${opportunity?.name || opportunityId}` }],
+ structuredContent: {
+ success: true,
+ opportunity: {
+ id: opportunity?.id,
+ name: opportunity?.name,
+ pipelineStageId: opportunity?.pipelineStageId,
+ monetaryValue: opportunity?.monetaryValue,
+ status: opportunity?.status
+ }
+ }
+ };
+ }
+
+ /**
+ * Create app tool result with structuredContent
+ */
+ private createAppResult(
+ textSummary: string,
+ resourceUri: string,
+ mimeType: string,
+ htmlContent: string,
+ data: any
+ ): AppToolResult {
+ // structuredContent is the data object that gets passed to ontoolresult
+ // The UI accesses it via result.structuredContent
+ return {
+ content: [{ type: 'text', text: textSummary }],
+ structuredContent: data
+ };
+ }
+
+ /**
+ * Inject data into HTML as a script tag
+ */
+ private injectDataIntoHTML(html: string, data: any): string {
+ const dataScript = ``;
+
+ // Insert before or at the beginning of
+ if (html.includes('')) {
+ return html.replace('', `${dataScript}`);
+ } else if (html.includes('')) {
+ return html.replace('', `${dataScript}`);
+ } else {
+ return dataScript + html;
+ }
+ }
+
+ /**
+ * Get resource handler by URI
+ */
+ getResourceHandler(uri: string): AppResourceHandler | undefined {
+ return this.resourceHandlers.get(uri);
+ }
+
+ /**
+ * Get all registered resource URIs
+ */
+ getResourceURIs(): string[] {
+ return Array.from(this.resourceHandlers.keys());
+ }
+}
diff --git a/mcp-diagrams/ghl-mcp-apps-only/src/clients/ghl-api-client.ts b/mcp-diagrams/ghl-mcp-apps-only/src/clients/ghl-api-client.ts
new file mode 100644
index 0000000..d5a307c
--- /dev/null
+++ b/mcp-diagrams/ghl-mcp-apps-only/src/clients/ghl-api-client.ts
@@ -0,0 +1,6858 @@
+/**
+ * GoHighLevel API Client
+ * Implements exact API endpoints from OpenAPI specifications v2021-07-28 (Contacts) and v2021-04-15 (Conversations)
+ */
+
+import axios, { AxiosInstance, AxiosResponse, AxiosError } from 'axios';
+import {
+ GHLConfig,
+ GHLContact,
+ GHLCreateContactRequest,
+ GHLSearchContactsRequest,
+ GHLSearchContactsResponse,
+ GHLContactTagsRequest,
+ GHLContactTagsResponse,
+ GHLApiResponse,
+ GHLErrorResponse,
+ GHLTask,
+ GHLNote,
+ // Conversation types
+ GHLConversation,
+ GHLMessage,
+ GHLSendMessageRequest,
+ GHLSendMessageResponse,
+ GHLSearchConversationsRequest,
+ GHLSearchConversationsResponse,
+ GHLGetMessagesResponse,
+ GHLCreateConversationRequest,
+ GHLCreateConversationResponse,
+ GHLUpdateConversationRequest,
+ // Blog types
+ GHLBlogPost,
+ GHLCreateBlogPostRequest,
+ GHLUpdateBlogPostRequest,
+ GHLBlogPostCreateResponse,
+ GHLBlogPostUpdateResponse,
+ GHLBlogPostListResponse,
+ GHLBlogAuthor,
+ GHLBlogAuthorsResponse,
+ GHLBlogCategory,
+ GHLBlogCategoriesResponse,
+ GHLBlogSite,
+ GHLBlogSitesResponse,
+ GHLUrlSlugCheckResponse,
+ GHLGetBlogPostsRequest,
+ GHLGetBlogAuthorsRequest,
+ GHLGetBlogCategoriesRequest,
+ GHLGetBlogSitesRequest,
+ GHLCheckUrlSlugRequest,
+ GHLSearchOpportunitiesRequest,
+ GHLSearchOpportunitiesResponse,
+ GHLGetPipelinesResponse,
+ GHLOpportunity,
+ GHLCreateOpportunityRequest,
+ GHLUpdateOpportunityRequest,
+ GHLOpportunityStatus,
+ GHLUpdateOpportunityStatusRequest,
+ GHLUpsertOpportunityRequest,
+ GHLUpsertOpportunityResponse,
+ GHLGetCalendarGroupsResponse,
+ GHLCreateCalendarGroupRequest,
+ GHLCalendarGroup,
+ GHLGetCalendarsResponse,
+ GHLCreateCalendarRequest,
+ GHLCalendar,
+ GHLUpdateCalendarRequest,
+ GHLGetCalendarEventsRequest,
+ GHLGetCalendarEventsResponse,
+ GHLGetFreeSlotsRequest,
+ GHLGetFreeSlotsResponse,
+ GHLCreateAppointmentRequest,
+ GHLCalendarEvent,
+ GHLUpdateAppointmentRequest,
+ GHLCreateBlockSlotRequest,
+ GHLBlockSlotResponse,
+ GHLUpdateBlockSlotRequest,
+ GHLEmailCampaignsResponse,
+ MCPGetEmailCampaignsParams,
+ MCPCreateEmailTemplateParams,
+ MCPGetEmailTemplatesParams,
+ MCPUpdateEmailTemplateParams,
+ MCPDeleteEmailTemplateParams,
+ GHLEmailTemplate,
+ // Location types
+ GHLLocationSearchResponse,
+ GHLLocationDetailsResponse,
+ GHLLocationDetailed,
+ GHLCreateLocationRequest,
+ GHLUpdateLocationRequest,
+ GHLLocationDeleteResponse,
+ GHLLocationTagsResponse,
+ GHLLocationTagResponse,
+ GHLLocationTagRequest,
+ GHLLocationTagDeleteResponse,
+ GHLLocationTaskSearchRequest,
+ GHLLocationTaskSearchResponse,
+ GHLLocationCustomFieldsResponse,
+ GHLLocationCustomFieldResponse,
+ GHLCreateCustomFieldRequest,
+ GHLUpdateCustomFieldRequest,
+ GHLCustomFieldDeleteResponse,
+ GHLFileUploadRequest,
+ GHLFileUploadResponse,
+ GHLLocationCustomValuesResponse,
+ GHLLocationCustomValueResponse,
+ GHLCustomValueRequest,
+ GHLCustomValueDeleteResponse,
+ GHLLocationTemplatesResponse,
+ // Email ISV types
+ GHLEmailVerificationRequest,
+ GHLEmailVerificationResponse,
+ // Additional Contact types
+ GHLAppointment,
+ GHLUpsertContactResponse,
+ GHLBulkTagsResponse,
+ GHLBulkBusinessResponse,
+ GHLFollowersResponse,
+ GHLCampaign,
+ GHLWorkflow,
+ // Additional Conversation/Message types
+ GHLEmailMessage,
+ GHLProcessInboundMessageRequest,
+ GHLProcessOutboundMessageRequest,
+ GHLProcessMessageResponse,
+ GHLCancelScheduledResponse,
+ GHLMessageRecordingResponse,
+ GHLMessageTranscription,
+ GHLMessageTranscriptionResponse,
+ GHLLiveChatTypingRequest,
+ GHLLiveChatTypingResponse,
+ GHLUploadFilesRequest,
+ GHLUploadFilesResponse,
+ GHLUpdateMessageStatusRequest,
+ // Social Media Posting API types
+ GHLSocialPlatform,
+ GHLSearchPostsRequest,
+ GHLSearchPostsResponse,
+ GHLCreatePostRequest,
+ GHLCreatePostResponse,
+ GHLUpdatePostRequest,
+ GHLGetPostResponse,
+ GHLBulkDeletePostsRequest,
+ GHLBulkDeleteResponse,
+ GHLGetAccountsResponse,
+ GHLUploadCSVRequest,
+ GHLUploadCSVResponse,
+ GHLGetUploadStatusResponse,
+ GHLSetAccountsRequest,
+ GHLCSVFinalizeRequest,
+ GHLGetCategoriesResponse,
+ GHLGetCategoryResponse,
+ GHLGetTagsResponse,
+ GHLGetTagsByIdsRequest,
+ GHLGetTagsByIdsResponse,
+ GHLOAuthStartResponse,
+ GHLGetGoogleLocationsResponse,
+ GHLAttachGMBLocationRequest,
+ GHLGetFacebookPagesResponse,
+ GHLAttachFBAccountRequest,
+ GHLGetInstagramAccountsResponse,
+ GHLAttachIGAccountRequest,
+ GHLGetLinkedInAccountsResponse,
+ GHLAttachLinkedInAccountRequest,
+ GHLGetTwitterAccountsResponse,
+ GHLAttachTwitterAccountRequest,
+ GHLGetTikTokAccountsResponse,
+ GHLAttachTikTokAccountRequest,
+ GHLCSVImport,
+ GHLSocialPost,
+ GHLSocialAccount,
+ GHLValidateGroupSlugResponse,
+ GHLGroupSuccessResponse,
+ GHLGroupStatusUpdateRequest,
+ GHLUpdateCalendarGroupRequest,
+ GHLGetAppointmentNotesResponse,
+ GHLCreateAppointmentNoteRequest,
+ GHLAppointmentNoteResponse,
+ GHLUpdateAppointmentNoteRequest,
+ GHLDeleteAppointmentNoteResponse,
+ GHLCalendarResource,
+ GHLCreateCalendarResourceRequest,
+ GHLCalendarResourceResponse,
+ GHLCalendarResourceByIdResponse,
+ GHLUpdateCalendarResourceRequest,
+ GHLResourceDeleteResponse,
+ GHLCalendarNotification,
+ GHLCreateCalendarNotificationRequest,
+ GHLUpdateCalendarNotificationRequest,
+ GHLCalendarNotificationDeleteResponse,
+ GHLGetCalendarNotificationsRequest,
+ GHLGetBlockedSlotsRequest,
+ GHLGetMediaFilesRequest,
+ GHLGetMediaFilesResponse,
+ GHLUploadMediaFileRequest,
+ GHLUploadMediaFileResponse,
+ GHLDeleteMediaRequest,
+ GHLDeleteMediaResponse,
+ // Custom Objects API types
+ GHLGetObjectSchemaRequest,
+ GHLGetObjectSchemaResponse,
+ GHLObjectListResponse,
+ GHLCreateObjectSchemaRequest,
+ GHLObjectSchemaResponse,
+ GHLUpdateObjectSchemaRequest,
+ GHLCreateObjectRecordRequest,
+ GHLObjectRecordResponse,
+ GHLDetailedObjectRecordResponse,
+ GHLUpdateObjectRecordRequest,
+ GHLObjectRecordDeleteResponse,
+ GHLSearchObjectRecordsRequest,
+ GHLSearchObjectRecordsResponse,
+ // Associations API types
+ GHLAssociation,
+ GHLRelation,
+ GHLCreateAssociationRequest,
+ GHLUpdateAssociationRequest,
+ GHLCreateRelationRequest,
+ GHLGetAssociationsRequest,
+ GHLGetRelationsByRecordRequest,
+ GHLGetAssociationByKeyRequest,
+ GHLGetAssociationByObjectKeyRequest,
+ GHLDeleteRelationRequest,
+ GHLAssociationResponse,
+ GHLDeleteAssociationResponse,
+ GHLGetAssociationsResponse,
+ GHLGetRelationsResponse,
+ // Custom Fields V2 API types
+ GHLV2CustomField,
+ GHLV2CustomFieldFolder,
+ GHLV2CreateCustomFieldRequest,
+ GHLV2UpdateCustomFieldRequest,
+ GHLV2CreateCustomFieldFolderRequest,
+ GHLV2UpdateCustomFieldFolderRequest,
+ GHLV2GetCustomFieldsByObjectKeyRequest,
+ GHLV2DeleteCustomFieldFolderRequest,
+ GHLV2CustomFieldResponse,
+ GHLV2CustomFieldsResponse,
+ GHLV2CustomFieldFolderResponse,
+ GHLV2DeleteCustomFieldResponse,
+ // Workflows API types
+ GHLGetWorkflowsRequest,
+ GHLGetWorkflowsResponse,
+ // Surveys API types
+ GHLGetSurveysRequest,
+ GHLGetSurveysResponse,
+ GHLGetSurveySubmissionsRequest,
+ GHLGetSurveySubmissionsResponse,
+ // Store API types
+ GHLCreateShippingZoneRequest,
+ GHLCreateShippingZoneResponse,
+ GHLListShippingZonesResponse,
+ GHLGetShippingZonesRequest,
+ GHLGetShippingZoneResponse,
+ GHLUpdateShippingZoneRequest,
+ GHLUpdateShippingZoneResponse,
+ GHLDeleteShippingZoneRequest,
+ GHLDeleteShippingZoneResponse,
+ GHLGetAvailableShippingRatesRequest,
+ GHLGetAvailableShippingRatesResponse,
+ GHLCreateShippingRateRequest,
+ GHLCreateShippingRateResponse,
+ GHLListShippingRatesResponse,
+ GHLGetShippingRatesRequest,
+ GHLGetShippingRateResponse,
+ GHLUpdateShippingRateRequest,
+ GHLUpdateShippingRateResponse,
+ GHLDeleteShippingRateRequest,
+ GHLDeleteShippingRateResponse,
+ GHLCreateShippingCarrierRequest,
+ GHLCreateShippingCarrierResponse,
+ GHLListShippingCarriersResponse,
+ GHLGetShippingCarriersRequest,
+ GHLGetShippingCarrierResponse,
+ GHLUpdateShippingCarrierRequest,
+ GHLUpdateShippingCarrierResponse,
+ GHLDeleteShippingCarrierRequest,
+ GHLDeleteShippingCarrierResponse,
+ GHLCreateStoreSettingRequest,
+ GHLCreateStoreSettingResponse,
+ GHLGetStoreSettingRequest,
+ GHLGetStoreSettingResponse,
+ GHLCreateProductRequest,
+ GHLCreateProductResponse,
+ GHLUpdateProductRequest,
+ GHLUpdateProductResponse,
+ GHLGetProductRequest,
+ GHLGetProductResponse,
+ GHLListProductsRequest,
+ GHLListProductsResponse,
+ GHLDeleteProductRequest,
+ GHLDeleteProductResponse,
+ GHLBulkUpdateRequest,
+ GHLBulkUpdateResponse,
+ GHLCreatePriceRequest,
+ GHLCreatePriceResponse,
+ GHLUpdatePriceRequest,
+ GHLUpdatePriceResponse,
+ GHLGetPriceRequest,
+ GHLGetPriceResponse,
+ GHLListPricesRequest,
+ GHLListPricesResponse,
+ GHLDeletePriceRequest,
+ GHLDeletePriceResponse,
+ GHLListInventoryRequest,
+ GHLListInventoryResponse,
+ GHLUpdateInventoryRequest,
+ GHLUpdateInventoryResponse,
+ GHLGetProductStoreStatsRequest,
+ GHLGetProductStoreStatsResponse,
+ GHLUpdateProductStoreRequest,
+ GHLUpdateProductStoreResponse,
+ GHLCreateProductCollectionRequest,
+ GHLCreateCollectionResponse,
+ GHLUpdateProductCollectionRequest,
+ GHLUpdateProductCollectionResponse,
+ GHLGetProductCollectionRequest,
+ GHLDefaultCollectionResponse,
+ GHLListProductCollectionsRequest,
+ GHLListCollectionResponse,
+ GHLDeleteProductCollectionRequest,
+ GHLDeleteProductCollectionResponse,
+ GHLListProductReviewsRequest,
+ GHLListProductReviewsResponse,
+ GHLGetReviewsCountRequest,
+ GHLCountReviewsByStatusResponse,
+ GHLUpdateProductReviewRequest,
+ GHLUpdateProductReviewsResponse,
+ GHLDeleteProductReviewRequest,
+ GHLDeleteProductReviewResponse,
+ GHLBulkUpdateProductReviewsRequest,
+ // Invoice API types
+ CreateInvoiceTemplateDto,
+ CreateInvoiceTemplateResponseDto,
+ UpdateInvoiceTemplateDto,
+ UpdateInvoiceTemplateResponseDto,
+ DeleteInvoiceTemplateResponseDto,
+ ListTemplatesResponse,
+ InvoiceTemplate,
+ UpdateInvoiceLateFeesConfigurationDto,
+ UpdatePaymentMethodsConfigurationDto,
+ CreateInvoiceScheduleDto,
+ CreateInvoiceScheduleResponseDto,
+ UpdateInvoiceScheduleDto,
+ UpdateInvoiceScheduleResponseDto,
+ DeleteInvoiceScheduleResponseDto,
+ ListSchedulesResponse,
+ GetScheduleResponseDto,
+ ScheduleInvoiceScheduleDto,
+ ScheduleInvoiceScheduleResponseDto,
+ AutoPaymentScheduleDto,
+ AutoPaymentInvoiceScheduleResponseDto,
+ CancelInvoiceScheduleDto,
+ CancelInvoiceScheduleResponseDto,
+ UpdateAndScheduleInvoiceScheduleResponseDto,
+ Text2PayDto,
+ Text2PayInvoiceResponseDto,
+ GenerateInvoiceNumberResponse,
+ GetInvoiceResponseDto,
+ UpdateInvoiceDto,
+ UpdateInvoiceResponseDto,
+ DeleteInvoiceResponseDto,
+ VoidInvoiceDto,
+ VoidInvoiceResponseDto,
+ SendInvoiceDto,
+ SendInvoicesResponseDto,
+ RecordPaymentDto,
+ RecordPaymentResponseDto,
+ PatchInvoiceStatsLastViewedDto,
+ CreateEstimatesDto,
+ EstimateResponseDto,
+ UpdateEstimateDto,
+ GenerateEstimateNumberResponse,
+ SendEstimateDto,
+ CreateInvoiceFromEstimateDto,
+ CreateInvoiceFromEstimateResponseDto,
+ ListEstimatesResponseDto,
+ EstimateIdParam,
+ ListEstimateTemplateResponseDto,
+ EstimateTemplatesDto,
+ EstimateTemplateResponseDto,
+ CreateInvoiceDto,
+ CreateInvoiceResponseDto,
+ ListInvoicesResponseDto,
+ AltDto
+} from '../types/ghl-types.js';
+
+/**
+ * GoHighLevel API Client
+ * Handles all API communication with GHL services
+ */
+export class GHLApiClient {
+ private axiosInstance: AxiosInstance;
+ private config: GHLConfig;
+
+ constructor(config: GHLConfig) {
+ this.config = config;
+
+ // Create axios instance with base configuration
+ this.axiosInstance = axios.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: AxiosError) => {
+ 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
+ */
+ private handleApiError(error: AxiosError): 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
+ */
+ private wrapResponse(data: T): GHLApiResponse {
+ return {
+ success: true,
+ data
+ };
+ }
+
+ /**
+ * Create custom headers for different API versions
+ */
+ private 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: GHLCreateContactRequest): Promise> {
+ try {
+ // Ensure locationId is set
+ const payload = {
+ ...contactData,
+ locationId: contactData.locationId || this.config.locationId
+ };
+
+ const response: AxiosResponse<{ contact: GHLContact }> = 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: string): Promise> {
+ try {
+ const response: AxiosResponse<{ contact: GHLContact }> = 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: string, updates: Partial): Promise> {
+ try {
+ const response: AxiosResponse<{ contact: GHLContact; succeded: boolean }> = 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: string): Promise> {
+ try {
+ const response: AxiosResponse<{ succeded: boolean }> = 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: GHLSearchContactsRequest): Promise> {
+ try {
+ // Build minimal request body with only required/supported parameters
+ // Start with just locationId and pageLimit as per API requirements
+ const payload: any = {
+ 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: any = {};
+ let hasFilters = false;
+
+ if (searchParams.filters.email && typeof searchParams.filters.email === 'string' && searchParams.filters.email.trim()) {
+ filters.email = searchParams.filters.email.trim();
+ hasFilters = true;
+ }
+
+ if (searchParams.filters.phone && typeof searchParams.filters.phone === 'string' && 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: AxiosResponse = await this.axiosInstance.post(
+ '/contacts/search',
+ payload
+ );
+
+ return this.wrapResponse(response.data);
+ } catch (error) {
+ const axiosError = error as AxiosError;
+ 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?: string, phone?: string): Promise> {
+ try {
+ const params: any = {
+ locationId: this.config.locationId
+ };
+
+ if (email) params.email = encodeURIComponent(email);
+ if (phone) params.number = encodeURIComponent(phone);
+
+ const response: AxiosResponse<{ contact?: GHLContact }> = 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: string, tags: string[]): Promise> {
+ try {
+ const payload: GHLContactTagsRequest = { tags };
+
+ const response: AxiosResponse = 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: string, tags: string[]): Promise> {
+ try {
+ const payload: GHLContactTagsRequest = { tags };
+
+ const response: AxiosResponse = 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: GHLSearchConversationsRequest): Promise> {
+ try {
+ // Ensure locationId is set
+ const params = {
+ ...searchParams,
+ locationId: searchParams.locationId || this.config.locationId
+ };
+
+ const response: AxiosResponse = 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: string): Promise> {
+ try {
+ const response: AxiosResponse = 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: GHLCreateConversationRequest): Promise> {
+ try {
+ // Ensure locationId is set
+ const payload = {
+ ...conversationData,
+ locationId: conversationData.locationId || this.config.locationId
+ };
+
+ const response: AxiosResponse<{ success: boolean; conversation: GHLCreateConversationResponse }> = 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: string, updates: GHLUpdateConversationRequest): Promise> {
+ try {
+ // Ensure locationId is set
+ const payload = {
+ ...updates,
+ locationId: updates.locationId || this.config.locationId
+ };
+
+ const response: AxiosResponse<{ success: boolean; conversation: GHLConversation }> = 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: string): Promise> {
+ try {
+ const response: AxiosResponse<{ success: boolean }> = 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: string,
+ options?: {
+ lastMessageId?: string;
+ limit?: number;
+ type?: string;
+ }
+ ): Promise> {
+ try {
+ const params: any = {};
+ if (options?.lastMessageId) params.lastMessageId = options.lastMessageId;
+ if (options?.limit) params.limit = options.limit;
+ if (options?.type) params.type = options.type;
+
+ const response: AxiosResponse = 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: string): Promise> {
+ try {
+ const response: AxiosResponse = 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: GHLSendMessageRequest): Promise> {
+ try {
+ const response: AxiosResponse = 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: string, message: string, fromNumber?: string): Promise> {
+ try {
+ const messageData: GHLSendMessageRequest = {
+ 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: string,
+ subject: string,
+ message?: string,
+ html?: string,
+ options?: {
+ emailFrom?: string;
+ emailTo?: string;
+ emailCc?: string[];
+ emailBcc?: string[];
+ attachments?: string[];
+ }
+ ): Promise> {
+ try {
+ const messageData: GHLSendMessageRequest = {
+ 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: GHLGetBlogSitesRequest): Promise> {
+ 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: AxiosResponse = 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: GHLGetBlogPostsRequest): Promise> {
+ 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: AxiosResponse = 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: GHLCreateBlogPostRequest): Promise> {
+ try {
+ // Ensure locationId is set
+ const payload = {
+ ...postData,
+ locationId: postData.locationId || this.config.locationId
+ };
+
+ const response: AxiosResponse = 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: string, postData: GHLUpdateBlogPostRequest): Promise> {
+ try {
+ // Ensure locationId is set
+ const payload = {
+ ...postData,
+ locationId: postData.locationId || this.config.locationId
+ };
+
+ const response: AxiosResponse = 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: GHLGetBlogAuthorsRequest): Promise> {
+ try {
+ // Ensure locationId is set
+ const queryParams = {
+ locationId: params.locationId || this.config.locationId,
+ limit: params.limit,
+ offset: params.offset
+ };
+
+ const response: AxiosResponse = 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: GHLGetBlogCategoriesRequest): Promise> {
+ try {
+ // Ensure locationId is set
+ const queryParams = {
+ locationId: params.locationId || this.config.locationId,
+ limit: params.limit,
+ offset: params.offset
+ };
+
+ const response: AxiosResponse = 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: GHLCheckUrlSlugRequest): Promise> {
+ try {
+ // Ensure locationId is set
+ const queryParams = {
+ locationId: params.locationId || this.config.locationId,
+ urlSlug: params.urlSlug,
+ ...(params.postId && { postId: params.postId })
+ };
+
+ const response: AxiosResponse = 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: string): Promise> {
+ try {
+ const response: AxiosResponse<{ tasks: GHLTask[] }> = 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: string, taskData: Omit): Promise> {
+ try {
+ const response: AxiosResponse<{ task: GHLTask }> = 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: string): Promise> {
+ try {
+ const response: AxiosResponse<{ notes: GHLNote[] }> = 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: string, noteData: Omit): Promise> {
+ try {
+ const response: AxiosResponse<{ note: GHLNote }> = 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: string, taskId: string): Promise> {
+ try {
+ const response: AxiosResponse<{ task: GHLTask }> = await this.axiosInstance.get(
+ `/contacts/${contactId}/tasks/${taskId}`
+ );
+
+ return this.wrapResponse(response.data.task);
+ } catch (error) {
+ throw this.handleApiError(error as AxiosError);
+ }
+ }
+
+ /**
+ * Update a task for a contact
+ * PUT /contacts/{contactId}/tasks/{taskId}
+ */
+ async updateContactTask(contactId: string, taskId: string, updates: Partial): Promise> {
+ try {
+ const response: AxiosResponse<{ task: GHLTask }> = await this.axiosInstance.put(
+ `/contacts/${contactId}/tasks/${taskId}`,
+ updates
+ );
+
+ return this.wrapResponse(response.data.task);
+ } catch (error) {
+ throw this.handleApiError(error as AxiosError);
+ }
+ }
+
+ /**
+ * Delete a task for a contact
+ * DELETE /contacts/{contactId}/tasks/{taskId}
+ */
+ async deleteContactTask(contactId: string, taskId: string): Promise> {
+ try {
+ const response: AxiosResponse<{ succeded: boolean }> = await this.axiosInstance.delete(
+ `/contacts/${contactId}/tasks/${taskId}`
+ );
+
+ return this.wrapResponse(response.data);
+ } catch (error) {
+ throw this.handleApiError(error as AxiosError);
+ }
+ }
+
+ /**
+ * Update task completion status
+ * PUT /contacts/{contactId}/tasks/{taskId}/completed
+ */
+ async updateTaskCompletion(contactId: string, taskId: string, completed: boolean): Promise> {
+ try {
+ const response: AxiosResponse<{ task: GHLTask }> = await this.axiosInstance.put(
+ `/contacts/${contactId}/tasks/${taskId}/completed`,
+ { completed }
+ );
+
+ return this.wrapResponse(response.data.task);
+ } catch (error) {
+ throw this.handleApiError(error as AxiosError);
+ }
+ }
+
+ /**
+ * Get a specific note for a contact
+ * GET /contacts/{contactId}/notes/{noteId}
+ */
+ async getContactNote(contactId: string, noteId: string): Promise> {
+ try {
+ const response: AxiosResponse<{ note: GHLNote }> = await this.axiosInstance.get(
+ `/contacts/${contactId}/notes/${noteId}`
+ );
+
+ return this.wrapResponse(response.data.note);
+ } catch (error) {
+ throw this.handleApiError(error as AxiosError);
+ }
+ }
+
+ /**
+ * Update a note for a contact
+ * PUT /contacts/{contactId}/notes/{noteId}
+ */
+ async updateContactNote(contactId: string, noteId: string, updates: Partial): Promise> {
+ try {
+ const response: AxiosResponse<{ note: GHLNote }> = await this.axiosInstance.put(
+ `/contacts/${contactId}/notes/${noteId}`,
+ updates
+ );
+
+ return this.wrapResponse(response.data.note);
+ } catch (error) {
+ throw this.handleApiError(error as AxiosError);
+ }
+ }
+
+ /**
+ * Delete a note for a contact
+ * DELETE /contacts/{contactId}/notes/{noteId}
+ */
+ async deleteContactNote(contactId: string, noteId: string): Promise> {
+ try {
+ const response: AxiosResponse<{ succeded: boolean }> = await this.axiosInstance.delete(
+ `/contacts/${contactId}/notes/${noteId}`
+ );
+
+ return this.wrapResponse(response.data);
+ } catch (error) {
+ throw this.handleApiError(error as AxiosError);
+ }
+ }
+
+ /**
+ * Upsert contact (create or update based on email/phone)
+ * POST /contacts/upsert
+ */
+ async upsertContact(contactData: Partial): Promise> {
+ try {
+ const payload = {
+ ...contactData,
+ locationId: contactData.locationId || this.config.locationId
+ };
+
+ const response: AxiosResponse = await this.axiosInstance.post(
+ '/contacts/upsert',
+ payload
+ );
+
+ return this.wrapResponse(response.data);
+ } catch (error) {
+ throw this.handleApiError(error as AxiosError);
+ }
+ }
+
+ /**
+ * Get contacts by business ID
+ * GET /contacts/business/{businessId}
+ */
+ async getContactsByBusiness(businessId: string, params: { limit?: number; skip?: number; query?: string } = {}): Promise> {
+ try {
+ const queryParams = {
+ limit: params.limit || 25,
+ skip: params.skip || 0,
+ ...(params.query && { query: params.query })
+ };
+
+ const response: AxiosResponse = await this.axiosInstance.get(
+ `/contacts/business/${businessId}`,
+ { params: queryParams }
+ );
+
+ return this.wrapResponse(response.data);
+ } catch (error) {
+ throw this.handleApiError(error as AxiosError);
+ }
+ }
+
+ /**
+ * Get contact appointments
+ * GET /contacts/{contactId}/appointments
+ */
+ async getContactAppointments(contactId: string): Promise> {
+ try {
+ const response: AxiosResponse<{ events: GHLAppointment[] }> = await this.axiosInstance.get(
+ `/contacts/${contactId}/appointments`
+ );
+
+ return this.wrapResponse(response.data.events);
+ } catch (error) {
+ throw this.handleApiError(error as AxiosError);
+ }
+ }
+
+ /**
+ * Bulk update contact tags
+ * POST /contacts/tags/bulk
+ */
+ async bulkUpdateContactTags(contactIds: string[], tags: string[], operation: 'add' | 'remove', removeAllTags?: boolean): Promise> {
+ try {
+ const payload = {
+ ids: contactIds,
+ tags,
+ operation,
+ ...(removeAllTags !== undefined && { removeAllTags })
+ };
+
+ const response: AxiosResponse = await this.axiosInstance.post(
+ '/contacts/tags/bulk',
+ payload
+ );
+
+ return this.wrapResponse(response.data);
+ } catch (error) {
+ throw this.handleApiError(error as AxiosError);
+ }
+ }
+
+ /**
+ * Bulk update contact business
+ * POST /contacts/business/bulk
+ */
+ async bulkUpdateContactBusiness(contactIds: string[], businessId?: string): Promise> {
+ try {
+ const payload = {
+ ids: contactIds,
+ businessId: businessId || null
+ };
+
+ const response: AxiosResponse = await this.axiosInstance.post(
+ '/contacts/business/bulk',
+ payload
+ );
+
+ return this.wrapResponse(response.data);
+ } catch (error) {
+ throw this.handleApiError(error as AxiosError);
+ }
+ }
+
+ /**
+ * Add contact followers
+ * POST /contacts/{contactId}/followers
+ */
+ async addContactFollowers(contactId: string, followers: string[]): Promise> {
+ try {
+ const payload = { followers };
+
+ const response: AxiosResponse = await this.axiosInstance.post(
+ `/contacts/${contactId}/followers`,
+ payload
+ );
+
+ return this.wrapResponse(response.data);
+ } catch (error) {
+ throw this.handleApiError(error as AxiosError);
+ }
+ }
+
+ /**
+ * Remove contact followers
+ * DELETE /contacts/{contactId}/followers
+ */
+ async removeContactFollowers(contactId: string, followers: string[]): Promise> {
+ try {
+ const payload = { followers };
+
+ const response: AxiosResponse = await this.axiosInstance.delete(
+ `/contacts/${contactId}/followers`,
+ { data: payload }
+ );
+
+ return this.wrapResponse(response.data);
+ } catch (error) {
+ throw this.handleApiError(error as AxiosError);
+ }
+ }
+
+ /**
+ * Add contact to campaign
+ * POST /contacts/{contactId}/campaigns/{campaignId}
+ */
+ async addContactToCampaign(contactId: string, campaignId: string): Promise> {
+ try {
+ const response: AxiosResponse<{ succeded: boolean }> = await this.axiosInstance.post(
+ `/contacts/${contactId}/campaigns/${campaignId}`
+ );
+
+ return this.wrapResponse(response.data);
+ } catch (error) {
+ throw this.handleApiError(error as AxiosError);
+ }
+ }
+
+ /**
+ * Remove contact from campaign
+ * DELETE /contacts/{contactId}/campaigns/{campaignId}
+ */
+ async removeContactFromCampaign(contactId: string, campaignId: string): Promise> {
+ try {
+ const response: AxiosResponse<{ succeded: boolean }> = await this.axiosInstance.delete(
+ `/contacts/${contactId}/campaigns/${campaignId}`
+ );
+
+ return this.wrapResponse(response.data);
+ } catch (error) {
+ throw this.handleApiError(error as AxiosError);
+ }
+ }
+
+ /**
+ * Remove contact from all campaigns
+ * DELETE /contacts/{contactId}/campaigns
+ */
+ async removeContactFromAllCampaigns(contactId: string): Promise> {
+ try {
+ const response: AxiosResponse<{ succeded: boolean }> = await this.axiosInstance.delete(
+ `/contacts/${contactId}/campaigns`
+ );
+
+ return this.wrapResponse(response.data);
+ } catch (error) {
+ throw this.handleApiError(error as AxiosError);
+ }
+ }
+
+ /**
+ * Add contact to workflow
+ * POST /contacts/{contactId}/workflow/{workflowId}
+ */
+ async addContactToWorkflow(contactId: string, workflowId: string, eventStartTime?: string): Promise> {
+ try {
+ const payload = eventStartTime ? { eventStartTime } : {};
+
+ const response: AxiosResponse<{ succeded: boolean }> = await this.axiosInstance.post(
+ `/contacts/${contactId}/workflow/${workflowId}`,
+ payload
+ );
+
+ return this.wrapResponse(response.data);
+ } catch (error) {
+ throw this.handleApiError(error as AxiosError);
+ }
+ }
+
+ /**
+ * Remove contact from workflow
+ * DELETE /contacts/{contactId}/workflow/{workflowId}
+ */
+ async removeContactFromWorkflow(contactId: string, workflowId: string, eventStartTime?: string): Promise> {
+ try {
+ const payload = eventStartTime ? { eventStartTime } : {};
+
+ const response: AxiosResponse<{ succeded: boolean }> = await this.axiosInstance.delete(
+ `/contacts/${contactId}/workflow/${workflowId}`,
+ { data: payload }
+ );
+
+ return this.wrapResponse(response.data);
+ } catch (error) {
+ throw this.handleApiError(error as AxiosError);
+ }
+ }
+
+ /**
+ * UTILITY METHODS
+ */
+
+ /**
+ * Test API connection and authentication
+ */
+ async testConnection(): Promise> {
+ try {
+ // Test with a simple GET request to check API connectivity
+ const response: AxiosResponse = 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: string): void {
+ this.config.accessToken = newToken;
+ this.axiosInstance.defaults.headers['Authorization'] = `Bearer ${newToken}`;
+ process.stderr.write('[GHL API] Access token updated\n');
+ }
+
+ /**
+ * Get current configuration
+ */
+ getConfig(): Readonly {
+ return { ...this.config };
+ }
+
+ /**
+ * Generic request method for new endpoints
+ * Used by new tool modules that don't have specific client methods yet
+ */
+ async makeRequest(method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE', path: string, body?: Record): Promise> {
+ try {
+ let response;
+ switch (method) {
+ case 'GET':
+ response = await this.axiosInstance.get(path);
+ break;
+ case 'POST':
+ response = await this.axiosInstance.post(path, body);
+ break;
+ case 'PUT':
+ response = await this.axiosInstance.put(path, body);
+ break;
+ case 'PATCH':
+ response = await this.axiosInstance.patch(path, body);
+ break;
+ case 'DELETE':
+ response = await this.axiosInstance.delete(path);
+ break;
+ }
+ return this.wrapResponse(response.data);
+ } catch (error) {
+ throw error;
+ }
+ }
+
+ /**
+ * OPPORTUNITIES API METHODS
+ */
+
+ /**
+ * Search opportunities with advanced filters
+ * GET /opportunities/search
+ */
+ async searchOpportunities(searchParams: GHLSearchOpportunitiesRequest): Promise> {
+ try {
+ // Build query parameters with exact API naming (underscores)
+ const params: any = {
+ 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: AxiosResponse = await this.axiosInstance.get(
+ '/opportunities/search',
+ { params }
+ );
+
+ return this.wrapResponse(response.data);
+ } catch (error) {
+ const axiosError = error as AxiosError;
+ 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?: string): Promise> {
+ try {
+ const params = {
+ locationId: locationId || this.config.locationId
+ };
+
+ const response: AxiosResponse = await this.axiosInstance.get(
+ '/opportunities/pipelines',
+ { params }
+ );
+
+ return this.wrapResponse(response.data);
+ } catch (error) {
+ throw this.handleApiError(error as AxiosError);
+ }
+ }
+
+ /**
+ * Get opportunity by ID
+ * GET /opportunities/{id}
+ */
+ async getOpportunity(opportunityId: string): Promise> {
+ try {
+ const response: AxiosResponse<{ opportunity: GHLOpportunity }> = await this.axiosInstance.get(
+ `/opportunities/${opportunityId}`
+ );
+
+ return this.wrapResponse(response.data.opportunity);
+ } catch (error) {
+ throw this.handleApiError(error as AxiosError);
+ }
+ }
+
+ /**
+ * Create a new opportunity
+ * POST /opportunities/
+ */
+ async createOpportunity(opportunityData: GHLCreateOpportunityRequest): Promise> {
+ try {
+ // Ensure locationId is set
+ const payload = {
+ ...opportunityData,
+ locationId: opportunityData.locationId || this.config.locationId
+ };
+
+ const response: AxiosResponse<{ opportunity: GHLOpportunity }> = await this.axiosInstance.post(
+ '/opportunities/',
+ payload
+ );
+
+ return this.wrapResponse(response.data.opportunity);
+ } catch (error) {
+ throw this.handleApiError(error as AxiosError);
+ }
+ }
+
+ /**
+ * Update existing opportunity
+ * PUT /opportunities/{id}
+ */
+ async updateOpportunity(opportunityId: string, updates: GHLUpdateOpportunityRequest): Promise> {
+ try {
+ const response: AxiosResponse<{ opportunity: GHLOpportunity }> = await this.axiosInstance.put(
+ `/opportunities/${opportunityId}`,
+ updates
+ );
+
+ return this.wrapResponse(response.data.opportunity);
+ } catch (error) {
+ throw this.handleApiError(error as AxiosError);
+ }
+ }
+
+ /**
+ * Update opportunity status
+ * PUT /opportunities/{id}/status
+ */
+ async updateOpportunityStatus(opportunityId: string, status: GHLOpportunityStatus): Promise> {
+ try {
+ const payload: GHLUpdateOpportunityStatusRequest = { status };
+
+ const response: AxiosResponse<{ succeded: boolean }> = await this.axiosInstance.put(
+ `/opportunities/${opportunityId}/status`,
+ payload
+ );
+
+ return this.wrapResponse(response.data);
+ } catch (error) {
+ throw this.handleApiError(error as AxiosError);
+ }
+ }
+
+ /**
+ * Upsert opportunity (create or update)
+ * POST /opportunities/upsert
+ */
+ async upsertOpportunity(opportunityData: GHLUpsertOpportunityRequest): Promise> {
+ try {
+ // Ensure locationId is set
+ const payload = {
+ ...opportunityData,
+ locationId: opportunityData.locationId || this.config.locationId
+ };
+
+ const response: AxiosResponse = await this.axiosInstance.post(
+ '/opportunities/upsert',
+ payload
+ );
+
+ return this.wrapResponse(response.data);
+ } catch (error) {
+ throw this.handleApiError(error as AxiosError);
+ }
+ }
+
+ /**
+ * Delete opportunity
+ * DELETE /opportunities/{id}
+ */
+ async deleteOpportunity(opportunityId: string): Promise> {
+ try {
+ const response: AxiosResponse<{ succeded: boolean }> = await this.axiosInstance.delete(
+ `/opportunities/${opportunityId}`
+ );
+
+ return this.wrapResponse(response.data);
+ } catch (error) {
+ throw this.handleApiError(error as AxiosError);
+ }
+ }
+
+ /**
+ * Add followers to opportunity
+ * POST /opportunities/{id}/followers
+ */
+ async addOpportunityFollowers(opportunityId: string, followers: string[]): Promise> {
+ try {
+ const payload = { followers };
+
+ const response: AxiosResponse = await this.axiosInstance.post(
+ `/opportunities/${opportunityId}/followers`,
+ payload
+ );
+
+ return this.wrapResponse(response.data);
+ } catch (error) {
+ throw this.handleApiError(error as AxiosError);
+ }
+ }
+
+ /**
+ * Remove followers from opportunity
+ * DELETE /opportunities/{id}/followers
+ */
+ async removeOpportunityFollowers(opportunityId: string, followers: string[]): Promise> {
+ try {
+ const payload = { followers };
+
+ const response: AxiosResponse = await this.axiosInstance.delete(
+ `/opportunities/${opportunityId}/followers`,
+ { data: payload }
+ );
+
+ return this.wrapResponse(response.data);
+ } catch (error) {
+ throw this.handleApiError(error as AxiosError);
+ }
+ }
+
+ /**
+ * CALENDAR & APPOINTMENTS API METHODS
+ */
+
+ /**
+ * Get all calendar groups in a location
+ * GET /calendars/groups
+ */
+ async getCalendarGroups(locationId?: string): Promise> {
+ try {
+ const params = {
+ locationId: locationId || this.config.locationId
+ };
+
+ const response: AxiosResponse = await this.axiosInstance.get(
+ '/calendars/groups',
+ { params }
+ );
+
+ return this.wrapResponse(response.data);
+ } catch (error) {
+ throw this.handleApiError(error as AxiosError);
+ }
+ }
+
+ /**
+ * Create a new calendar group
+ * POST /calendars/groups
+ */
+ async createCalendarGroup(groupData: GHLCreateCalendarGroupRequest): Promise> {
+ try {
+ const payload = {
+ ...groupData,
+ locationId: groupData.locationId || this.config.locationId
+ };
+
+ const response: AxiosResponse<{ group: GHLCalendarGroup }> = await this.axiosInstance.post(
+ '/calendars/groups',
+ payload
+ );
+
+ return this.wrapResponse(response.data);
+ } catch (error) {
+ throw this.handleApiError(error as AxiosError);
+ }
+ }
+
+ /**
+ * Get all calendars in a location
+ * GET /calendars/
+ */
+ async getCalendars(params?: { locationId?: string; groupId?: string; showDrafted?: boolean }): Promise> {
+ try {
+ const queryParams = {
+ locationId: params?.locationId || this.config.locationId,
+ ...(params?.groupId && { groupId: params.groupId }),
+ ...(params?.showDrafted !== undefined && { showDrafted: params.showDrafted })
+ };
+
+ const response: AxiosResponse = await this.axiosInstance.get(
+ '/calendars/',
+ { params: queryParams }
+ );
+
+ return this.wrapResponse(response.data);
+ } catch (error) {
+ throw this.handleApiError(error as AxiosError);
+ }
+ }
+
+ /**
+ * Create a new calendar
+ * POST /calendars/
+ */
+ async createCalendar(calendarData: GHLCreateCalendarRequest): Promise> {
+ try {
+ const payload = {
+ ...calendarData,
+ locationId: calendarData.locationId || this.config.locationId
+ };
+
+ const response: AxiosResponse<{ calendar: GHLCalendar }> = await this.axiosInstance.post(
+ '/calendars/',
+ payload
+ );
+
+ return this.wrapResponse(response.data);
+ } catch (error) {
+ throw this.handleApiError(error as AxiosError);
+ }
+ }
+
+ /**
+ * Get calendar by ID
+ * GET /calendars/{calendarId}
+ */
+ async getCalendar(calendarId: string): Promise> {
+ try {
+ const response: AxiosResponse<{ calendar: GHLCalendar }> = await this.axiosInstance.get(
+ `/calendars/${calendarId}`
+ );
+
+ return this.wrapResponse(response.data);
+ } catch (error) {
+ throw this.handleApiError(error as AxiosError);
+ }
+ }
+
+ /**
+ * Update calendar by ID
+ * PUT /calendars/{calendarId}
+ */
+ async updateCalendar(calendarId: string, updates: GHLUpdateCalendarRequest): Promise> {
+ try {
+ const response: AxiosResponse<{ calendar: GHLCalendar }> = await this.axiosInstance.put(
+ `/calendars/${calendarId}`,
+ updates
+ );
+
+ return this.wrapResponse(response.data);
+ } catch (error) {
+ throw this.handleApiError(error as AxiosError);
+ }
+ }
+
+ /**
+ * Delete calendar by ID
+ * DELETE /calendars/{calendarId}
+ */
+ async deleteCalendar(calendarId: string): Promise> {
+ try {
+ const response: AxiosResponse<{ success: boolean }> = await this.axiosInstance.delete(
+ `/calendars/${calendarId}`
+ );
+
+ return this.wrapResponse(response.data);
+ } catch (error) {
+ throw this.handleApiError(error as AxiosError);
+ }
+ }
+
+ /**
+ * Get calendar events/appointments
+ * GET /calendars/events
+ */
+ async getCalendarEvents(eventParams: GHLGetCalendarEventsRequest): Promise> {
+ 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: AxiosResponse = await this.axiosInstance.get(
+ '/calendars/events',
+ { params }
+ );
+
+ return this.wrapResponse(response.data);
+ } catch (error) {
+ throw this.handleApiError(error as AxiosError);
+ }
+ }
+
+ /**
+ * Get blocked slots
+ * GET /calendars/blocked-slots
+ */
+ async getBlockedSlots(eventParams: GHLGetCalendarEventsRequest): Promise> {
+ 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: AxiosResponse = await this.axiosInstance.get(
+ '/calendars/blocked-slots',
+ { params }
+ );
+
+ return this.wrapResponse(response.data);
+ } catch (error) {
+ throw this.handleApiError(error as AxiosError);
+ }
+ }
+
+ /**
+ * Get free slots for a calendar
+ * GET /calendars/{calendarId}/free-slots
+ */
+ async getFreeSlots(slotParams: GHLGetFreeSlotsRequest): Promise> {
+ 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: AxiosResponse = await this.axiosInstance.get(
+ `/calendars/${slotParams.calendarId}/free-slots`,
+ { params }
+ );
+
+ return this.wrapResponse(response.data);
+ } catch (error) {
+ throw this.handleApiError(error as AxiosError);
+ }
+ }
+
+ /**
+ * Create a new appointment
+ * POST /calendars/events/appointments
+ */
+ async createAppointment(appointmentData: GHLCreateAppointmentRequest): Promise> {
+ try {
+ const payload = {
+ ...appointmentData,
+ locationId: appointmentData.locationId || this.config.locationId
+ };
+
+ const response: AxiosResponse = await this.axiosInstance.post(
+ '/calendars/events/appointments',
+ payload
+ );
+
+ return this.wrapResponse(response.data);
+ } catch (error) {
+ throw this.handleApiError(error as AxiosError);
+ }
+ }
+
+ /**
+ * Get appointment by ID
+ * GET /calendars/events/appointments/{eventId}
+ */
+ async getAppointment(appointmentId: string): Promise> {
+ try {
+ const response: AxiosResponse<{ event: GHLCalendarEvent }> = await this.axiosInstance.get(
+ `/calendars/events/appointments/${appointmentId}`
+ );
+
+ return this.wrapResponse(response.data);
+ } catch (error) {
+ throw this.handleApiError(error as AxiosError);
+ }
+ }
+
+ /**
+ * Update appointment by ID
+ * PUT /calendars/events/appointments/{eventId}
+ */
+ async updateAppointment(appointmentId: string, updates: GHLUpdateAppointmentRequest): Promise> {
+ try {
+ const response: AxiosResponse = await this.axiosInstance.put(
+ `/calendars/events/appointments/${appointmentId}`,
+ updates
+ );
+
+ return this.wrapResponse(response.data);
+ } catch (error) {
+ throw this.handleApiError(error as AxiosError);
+ }
+ }
+
+ /**
+ * Delete appointment by ID
+ * DELETE /calendars/events/appointments/{eventId}
+ */
+ async deleteAppointment(appointmentId: string): Promise> {
+ try {
+ const response: AxiosResponse<{ succeeded: boolean }> = await this.axiosInstance.delete(
+ `/calendars/events/appointments/${appointmentId}`
+ );
+
+ return this.wrapResponse(response.data);
+ } catch (error) {
+ throw this.handleApiError(error as AxiosError);
+ }
+ }
+
+
+
+ /**
+ * Update block slot by ID
+ * PUT /calendars/events/block-slots/{eventId}
+ */
+ async updateBlockSlot(blockSlotId: string, updates: GHLUpdateBlockSlotRequest): Promise> {
+ try {
+ const response: AxiosResponse = await this.axiosInstance.put(
+ `/calendars/events/block-slots/${blockSlotId}`,
+ updates
+ );
+
+ return this.wrapResponse(response.data);
+ } catch (error) {
+ throw this.handleApiError(error as AxiosError);
+ }
+ }
+
+ /**
+ * EMAIL API METHODS
+ */
+
+ async getEmailCampaigns(params: MCPGetEmailCampaignsParams): Promise> {
+ try {
+ const response: AxiosResponse = await this.axiosInstance.get('/emails/schedule', {
+ params: {
+ locationId: this.config.locationId,
+ ...params
+ }
+ });
+ return this.wrapResponse(response.data);
+ } catch (error) {
+ throw this.handleApiError(error as AxiosError);
+ }
+ }
+
+ async createEmailTemplate(params: MCPCreateEmailTemplateParams): Promise> {
+ try {
+ const response: AxiosResponse = await this.axiosInstance.post('/emails/builder', {
+ locationId: this.config.locationId,
+ type: 'html',
+ ...params
+ });
+ return this.wrapResponse(response.data);
+ } catch (error) {
+ throw this.handleApiError(error as AxiosError);
+ }
+ }
+
+ async getEmailTemplates(params: MCPGetEmailTemplatesParams): Promise> {
+ try {
+ const response: AxiosResponse = await this.axiosInstance.get('/emails/builder', {
+ params: {
+ locationId: this.config.locationId,
+ ...params
+ }
+ });
+ return this.wrapResponse(response.data);
+ } catch (error) {
+ throw this.handleApiError(error as AxiosError);
+ }
+ }
+
+ async updateEmailTemplate(params: MCPUpdateEmailTemplateParams): Promise> {
+ try {
+ const { templateId, ...data } = params;
+ const response: AxiosResponse = 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 as AxiosError);
+ }
+ }
+
+ async deleteEmailTemplate(params: MCPDeleteEmailTemplateParams): Promise> {
+ try {
+ const { templateId } = params;
+ const response: AxiosResponse = await this.axiosInstance.delete(`/emails/builder/${this.config.locationId}/${templateId}`);
+ return this.wrapResponse(response.data);
+ } catch (error) {
+ throw this.handleApiError(error as AxiosError);
+ }
+ }
+
+ /**
+ * LOCATION API METHODS
+ */
+
+ /**
+ * Search locations/sub-accounts
+ * GET /locations/search
+ */
+ async searchLocations(params: {
+ companyId?: string;
+ skip?: number;
+ limit?: number;
+ order?: 'asc' | 'desc';
+ email?: string;
+ } = {}): Promise> {
+ 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: AxiosResponse = await this.axiosInstance.get(
+ '/locations/search',
+ { params: queryParams }
+ );
+
+ return this.wrapResponse(response.data);
+ } catch (error) {
+ throw this.handleApiError(error as AxiosError);
+ }
+ }
+
+ /**
+ * Get location by ID
+ * GET /locations/{locationId}
+ */
+ async getLocationById(locationId: string): Promise> {
+ try {
+ const response: AxiosResponse = await this.axiosInstance.get(
+ `/locations/${locationId}`
+ );
+
+ return this.wrapResponse(response.data);
+ } catch (error) {
+ throw this.handleApiError(error as AxiosError);
+ }
+ }
+
+ /**
+ * Create a new location/sub-account
+ * POST /locations/
+ */
+ async createLocation(locationData: GHLCreateLocationRequest): Promise> {
+ try {
+ const response: AxiosResponse = await this.axiosInstance.post(
+ '/locations/',
+ locationData
+ );
+
+ return this.wrapResponse(response.data);
+ } catch (error) {
+ throw this.handleApiError(error as AxiosError);
+ }
+ }
+
+ /**
+ * Update location/sub-account
+ * PUT /locations/{locationId}
+ */
+ async updateLocation(locationId: string, updates: GHLUpdateLocationRequest): Promise> {
+ try {
+ const response: AxiosResponse = await this.axiosInstance.put(
+ `/locations/${locationId}`,
+ updates
+ );
+
+ return this.wrapResponse(response.data);
+ } catch (error) {
+ throw this.handleApiError(error as AxiosError);
+ }
+ }
+
+ /**
+ * Delete location/sub-account
+ * DELETE /locations/{locationId}
+ */
+ async deleteLocation(locationId: string, deleteTwilioAccount: boolean): Promise> {
+ try {
+ const response: AxiosResponse = await this.axiosInstance.delete(
+ `/locations/${locationId}`,
+ {
+ params: { deleteTwilioAccount }
+ }
+ );
+
+ return this.wrapResponse(response.data);
+ } catch (error) {
+ throw this.handleApiError(error as AxiosError);
+ }
+ }
+
+ /**
+ * LOCATION TAGS API METHODS
+ */
+
+ /**
+ * Get location tags
+ * GET /locations/{locationId}/tags
+ */
+ async getLocationTags(locationId: string): Promise> {
+ try {
+ const response: AxiosResponse = await this.axiosInstance.get(
+ `/locations/${locationId}/tags`
+ );
+
+ return this.wrapResponse(response.data);
+ } catch (error) {
+ throw this.handleApiError(error as AxiosError);
+ }
+ }
+
+ /**
+ * Create location tag
+ * POST /locations/{locationId}/tags
+ */
+ async createLocationTag(locationId: string, tagData: GHLLocationTagRequest): Promise> {
+ try {
+ const response: AxiosResponse = await this.axiosInstance.post(
+ `/locations/${locationId}/tags`,
+ tagData
+ );
+
+ return this.wrapResponse(response.data);
+ } catch (error) {
+ throw this.handleApiError(error as AxiosError);
+ }
+ }
+
+ /**
+ * Get location tag by ID
+ * GET /locations/{locationId}/tags/{tagId}
+ */
+ async getLocationTag(locationId: string, tagId: string): Promise> {
+ try {
+ const response: AxiosResponse = await this.axiosInstance.get(
+ `/locations/${locationId}/tags/${tagId}`
+ );
+
+ return this.wrapResponse(response.data);
+ } catch (error) {
+ throw this.handleApiError(error as AxiosError);
+ }
+ }
+
+ /**
+ * Update location tag
+ * PUT /locations/{locationId}/tags/{tagId}
+ */
+ async updateLocationTag(locationId: string, tagId: string, tagData: GHLLocationTagRequest): Promise> {
+ try {
+ const response: AxiosResponse = await this.axiosInstance.put(
+ `/locations/${locationId}/tags/${tagId}`,
+ tagData
+ );
+
+ return this.wrapResponse(response.data);
+ } catch (error) {
+ throw this.handleApiError(error as AxiosError);
+ }
+ }
+
+ /**
+ * Delete location tag
+ * DELETE /locations/{locationId}/tags/{tagId}
+ */
+ async deleteLocationTag(locationId: string, tagId: string): Promise> {
+ try {
+ const response: AxiosResponse = await this.axiosInstance.delete(
+ `/locations/${locationId}/tags/${tagId}`
+ );
+
+ return this.wrapResponse(response.data);
+ } catch (error) {
+ throw this.handleApiError(error as AxiosError);
+ }
+ }
+
+ /**
+ * LOCATION TASKS API METHODS
+ */
+
+ /**
+ * Search location tasks
+ * POST /locations/{locationId}/tasks/search
+ */
+ async searchLocationTasks(locationId: string, searchParams: GHLLocationTaskSearchRequest): Promise> {
+ try {
+ const response: AxiosResponse = await this.axiosInstance.post(
+ `/locations/${locationId}/tasks/search`,
+ searchParams
+ );
+
+ return this.wrapResponse(response.data);
+ } catch (error) {
+ throw this.handleApiError(error as AxiosError);
+ }
+ }
+
+ /**
+ * CUSTOM FIELDS API METHODS
+ */
+
+ /**
+ * Get custom fields for location
+ * GET /locations/{locationId}/customFields
+ */
+ async getLocationCustomFields(locationId: string, model?: 'contact' | 'opportunity' | 'all'): Promise> {
+ try {
+ const params: any = {};
+ if (model) params.model = model;
+
+ const response: AxiosResponse = await this.axiosInstance.get(
+ `/locations/${locationId}/customFields`,
+ { params }
+ );
+
+ return this.wrapResponse(response.data);
+ } catch (error) {
+ throw this.handleApiError(error as AxiosError);
+ }
+ }
+
+ /**
+ * Create custom field for location
+ * POST /locations/{locationId}/customFields
+ */
+ async createLocationCustomField(locationId: string, fieldData: GHLCreateCustomFieldRequest): Promise> {
+ try {
+ const response: AxiosResponse = await this.axiosInstance.post(
+ `/locations/${locationId}/customFields`,
+ fieldData
+ );
+
+ return this.wrapResponse(response.data);
+ } catch (error) {
+ throw this.handleApiError(error as AxiosError);
+ }
+ }
+
+ /**
+ * Get custom field by ID
+ * GET /locations/{locationId}/customFields/{id}
+ */
+ async getLocationCustomField(locationId: string, customFieldId: string): Promise> {
+ try {
+ const response: AxiosResponse = await this.axiosInstance.get(
+ `/locations/${locationId}/customFields/${customFieldId}`
+ );
+
+ return this.wrapResponse(response.data);
+ } catch (error) {
+ throw this.handleApiError(error as AxiosError);
+ }
+ }
+
+ /**
+ * Update custom field
+ * PUT /locations/{locationId}/customFields/{id}
+ */
+ async updateLocationCustomField(locationId: string, customFieldId: string, fieldData: GHLUpdateCustomFieldRequest): Promise> {
+ try {
+ const response: AxiosResponse = await this.axiosInstance.put(
+ `/locations/${locationId}/customFields/${customFieldId}`,
+ fieldData
+ );
+
+ return this.wrapResponse(response.data);
+ } catch (error) {
+ throw this.handleApiError(error as AxiosError);
+ }
+ }
+
+ /**
+ * Delete custom field
+ * DELETE /locations/{locationId}/customFields/{id}
+ */
+ async deleteLocationCustomField(locationId: string, customFieldId: string): Promise> {
+ try {
+ const response: AxiosResponse = await this.axiosInstance.delete(
+ `/locations/${locationId}/customFields/${customFieldId}`
+ );
+
+ return this.wrapResponse(response.data);
+ } catch (error) {
+ throw this.handleApiError(error as AxiosError);
+ }
+ }
+
+ /**
+ * Upload file to custom fields
+ * POST /locations/{locationId}/customFields/upload
+ */
+ async uploadLocationCustomFieldFile(locationId: string, uploadData: GHLFileUploadRequest): Promise> {
+ try {
+ // Note: This endpoint expects multipart/form-data but we'll handle it as JSON for now
+ // In a real implementation, you'd use FormData for file uploads
+ const response: AxiosResponse = await this.axiosInstance.post(
+ `/locations/${locationId}/customFields/upload`,
+ uploadData,
+ { headers: { 'Content-Type': 'multipart/form-data' } }
+ );
+
+ return this.wrapResponse(response.data);
+ } catch (error) {
+ throw this.handleApiError(error as AxiosError);
+ }
+ }
+
+ /**
+ * CUSTOM VALUES API METHODS
+ */
+
+ /**
+ * Get custom values for location
+ * GET /locations/{locationId}/customValues
+ */
+ async getLocationCustomValues(locationId: string): Promise> {
+ try {
+ const response: AxiosResponse = await this.axiosInstance.get(
+ `/locations/${locationId}/customValues`
+ );
+
+ return this.wrapResponse(response.data);
+ } catch (error) {
+ throw this.handleApiError(error as AxiosError);
+ }
+ }
+
+ /**
+ * Create custom value for location
+ * POST /locations/{locationId}/customValues
+ */
+ async createLocationCustomValue(locationId: string, valueData: GHLCustomValueRequest): Promise