diff --git a/apps/docs/content/docs/en/tools/hubspot.mdx b/apps/docs/content/docs/en/tools/hubspot.mdx index b4cfe86af81..99cba9a5343 100644 --- a/apps/docs/content/docs/en/tools/hubspot.mdx +++ b/apps/docs/content/docs/en/tools/hubspot.mdx @@ -499,7 +499,7 @@ Retrieve all deals from HubSpot account with pagination support | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `limit` | string | No | Maximum number of results per page \(max 100, default 100\) | +| `limit` | string | No | Maximum number of results per page \(max 100, default 10\) | | `after` | string | No | Pagination cursor for next page of results \(from previous response\) | | `properties` | string | No | Comma-separated list of HubSpot property names to return \(e.g., "dealname,amount,dealstage"\) | | `associations` | string | No | Comma-separated list of object types to retrieve associated IDs for \(e.g., "contacts,companies"\) | diff --git a/apps/docs/content/docs/en/tools/quiver.mdx b/apps/docs/content/docs/en/tools/quiver.mdx index 2a5df8d1ca6..14fbe522fd8 100644 --- a/apps/docs/content/docs/en/tools/quiver.mdx +++ b/apps/docs/content/docs/en/tools/quiver.mdx @@ -59,8 +59,9 @@ Generate SVG images from text prompts using QuiverAI | --------- | ---- | ----------- | | `success` | boolean | Whether the SVG generation succeeded | | `output` | object | Generated SVG output | -| ↳ `file` | file | Generated SVG file | -| ↳ `svgContent` | string | Raw SVG markup content | +| ↳ `file` | file | First generated SVG file | +| ↳ `files` | json | All generated SVG files \(when n > 1\) | +| ↳ `svgContent` | string | Raw SVG markup content of the first result | | ↳ `id` | string | Generation request ID | | ↳ `usage` | json | Token usage statistics | | ↳ `totalTokens` | number | Total tokens used | diff --git a/apps/sim/blocks/blocks/hubspot.ts b/apps/sim/blocks/blocks/hubspot.ts index ecacf1d588e..36dee5e4dc6 100644 --- a/apps/sim/blocks/blocks/hubspot.ts +++ b/apps/sim/blocks/blocks/hubspot.ts @@ -25,7 +25,6 @@ export const HubSpotBlock: BlockConfig = { title: 'Operation', type: 'dropdown', options: [ - { label: 'Get Users', id: 'get_users' }, { label: 'Get Contacts', id: 'get_contacts' }, { label: 'Create Contact', id: 'create_contact' }, { label: 'Update Contact', id: 'update_contact' }, @@ -35,6 +34,26 @@ export const HubSpotBlock: BlockConfig = { { label: 'Update Company', id: 'update_company' }, { label: 'Search Companies', id: 'search_companies' }, { label: 'Get Deals', id: 'get_deals' }, + { label: 'Create Deal', id: 'create_deal' }, + { label: 'Update Deal', id: 'update_deal' }, + { label: 'Search Deals', id: 'search_deals' }, + { label: 'Get Tickets', id: 'get_tickets' }, + { label: 'Create Ticket', id: 'create_ticket' }, + { label: 'Update Ticket', id: 'update_ticket' }, + { label: 'Search Tickets', id: 'search_tickets' }, + { label: 'Get Line Items', id: 'get_line_items' }, + { label: 'Create Line Item', id: 'create_line_item' }, + { label: 'Update Line Item', id: 'update_line_item' }, + { label: 'Get Quotes', id: 'get_quotes' }, + { label: 'Get Appointments', id: 'get_appointments' }, + { label: 'Create Appointment', id: 'create_appointment' }, + { label: 'Update Appointment', id: 'update_appointment' }, + { label: 'Get Carts', id: 'get_carts' }, + { label: 'List Owners', id: 'list_owners' }, + { label: 'Get Marketing Events', id: 'get_marketing_events' }, + { label: 'Get Lists', id: 'get_lists' }, + { label: 'Create List', id: 'create_list' }, + { label: 'Get Users', id: 'get_users' }, ], value: () => 'get_contacts', }, @@ -88,6 +107,121 @@ export const HubSpotBlock: BlockConfig = { condition: { field: 'operation', value: 'update_company' }, required: true, }, + { + id: 'dealId', + title: 'Deal ID', + type: 'short-input', + placeholder: 'Leave empty to list all deals', + condition: { field: 'operation', value: 'get_deals' }, + }, + { + id: 'dealId', + title: 'Deal ID', + type: 'short-input', + placeholder: 'Numeric ID, or custom ID (requires ID Property below)', + condition: { field: 'operation', value: 'update_deal' }, + required: true, + }, + { + id: 'ticketId', + title: 'Ticket ID', + type: 'short-input', + placeholder: 'Leave empty to list all tickets', + condition: { field: 'operation', value: 'get_tickets' }, + }, + { + id: 'ticketId', + title: 'Ticket ID', + type: 'short-input', + placeholder: 'Numeric ID, or custom ID (requires ID Property below)', + condition: { field: 'operation', value: 'update_ticket' }, + required: true, + }, + { + id: 'lineItemId', + title: 'Line Item ID', + type: 'short-input', + placeholder: 'Leave empty to list all line items', + condition: { field: 'operation', value: 'get_line_items' }, + }, + { + id: 'lineItemId', + title: 'Line Item ID', + type: 'short-input', + placeholder: 'Numeric ID, or custom ID (requires ID Property below)', + condition: { field: 'operation', value: 'update_line_item' }, + required: true, + }, + { + id: 'quoteId', + title: 'Quote ID', + type: 'short-input', + placeholder: 'Leave empty to list all quotes', + condition: { field: 'operation', value: 'get_quotes' }, + }, + { + id: 'appointmentId', + title: 'Appointment ID', + type: 'short-input', + placeholder: 'Leave empty to list all appointments', + condition: { field: 'operation', value: 'get_appointments' }, + }, + { + id: 'appointmentId', + title: 'Appointment ID', + type: 'short-input', + placeholder: 'Numeric ID, or custom ID (requires ID Property below)', + condition: { field: 'operation', value: 'update_appointment' }, + required: true, + }, + { + id: 'cartId', + title: 'Cart ID', + type: 'short-input', + placeholder: 'Leave empty to list all carts', + condition: { field: 'operation', value: 'get_carts' }, + }, + { + id: 'eventId', + title: 'Marketing Event ID', + type: 'short-input', + placeholder: 'Leave empty to list all marketing events', + condition: { field: 'operation', value: 'get_marketing_events' }, + }, + { + id: 'listId', + title: 'List ID', + type: 'short-input', + placeholder: 'Leave empty to search all lists', + condition: { field: 'operation', value: 'get_lists' }, + }, + { + id: 'listName', + title: 'List Name', + type: 'short-input', + placeholder: 'Name for the new list', + condition: { field: 'operation', value: 'create_list' }, + required: true, + }, + { + id: 'objectTypeId', + title: 'Object Type ID', + type: 'short-input', + placeholder: 'e.g., "0-1" for contacts, "0-2" for companies', + condition: { field: 'operation', value: 'create_list' }, + required: true, + }, + { + id: 'processingType', + title: 'Processing Type', + type: 'dropdown', + options: [ + { label: 'Manual (Static)', id: 'MANUAL' }, + { label: 'Dynamic (Active)', id: 'DYNAMIC' }, + ], + condition: { field: 'operation', value: 'create_list' }, + required: true, + }, { id: 'idProperty', title: 'ID Property', @@ -95,7 +229,21 @@ export const HubSpotBlock: BlockConfig = { placeholder: 'Required if using email/domain (e.g., "email" or "domain")', condition: { field: 'operation', - value: ['get_contacts', 'update_contact', 'get_companies', 'update_company'], + value: [ + 'get_contacts', + 'update_contact', + 'get_companies', + 'update_company', + 'get_deals', + 'update_deal', + 'get_tickets', + 'update_ticket', + 'get_line_items', + 'update_line_item', + 'get_quotes', + 'get_appointments', + 'update_appointment', + ], }, }, { @@ -106,7 +254,20 @@ export const HubSpotBlock: BlockConfig = { 'JSON object with properties (e.g., {"email": "test@example.com", "firstname": "John"})', condition: { field: 'operation', - value: ['create_contact', 'update_contact', 'create_company', 'update_company'], + value: [ + 'create_contact', + 'update_contact', + 'create_company', + 'update_company', + 'create_deal', + 'update_deal', + 'create_ticket', + 'update_ticket', + 'create_line_item', + 'update_line_item', + 'create_appointment', + 'update_appointment', + ], }, wandConfig: { enabled: true, @@ -235,7 +396,19 @@ Return ONLY the JSON object with properties - no explanations, no markdown, no e title: 'Properties to Return', type: 'short-input', placeholder: 'Comma-separated list (e.g., "email,firstname,lastname")', - condition: { field: 'operation', value: ['get_contacts', 'get_companies', 'get_deals'] }, + condition: { + field: 'operation', + value: [ + 'get_contacts', + 'get_companies', + 'get_deals', + 'get_tickets', + 'get_line_items', + 'get_quotes', + 'get_appointments', + 'get_carts', + ], + }, }, { id: 'associations', @@ -244,7 +417,22 @@ Return ONLY the JSON object with properties - no explanations, no markdown, no e placeholder: 'Comma-separated object types (e.g., "companies,deals")', condition: { field: 'operation', - value: ['get_contacts', 'get_companies', 'get_deals', 'create_contact', 'create_company'], + value: [ + 'get_contacts', + 'get_companies', + 'get_deals', + 'get_tickets', + 'get_line_items', + 'get_quotes', + 'get_appointments', + 'get_carts', + 'create_contact', + 'create_company', + 'create_deal', + 'create_ticket', + 'create_line_item', + 'create_appointment', + ], }, }, { @@ -259,8 +447,18 @@ Return ONLY the JSON object with properties - no explanations, no markdown, no e 'get_contacts', 'get_companies', 'get_deals', + 'get_tickets', + 'get_line_items', + 'get_quotes', + 'get_appointments', + 'get_carts', + 'list_owners', + 'get_marketing_events', + 'get_lists', 'search_contacts', 'search_companies', + 'search_deals', + 'search_tickets', ], }, }, @@ -275,8 +473,19 @@ Return ONLY the JSON object with properties - no explanations, no markdown, no e 'get_contacts', 'get_companies', 'get_deals', + 'get_tickets', + 'get_line_items', + 'get_quotes', + 'get_appointments', + 'get_carts', + 'list_owners', + 'get_users', + 'get_marketing_events', + 'get_lists', 'search_contacts', 'search_companies', + 'search_deals', + 'search_tickets', ], }, }, @@ -285,7 +494,16 @@ Return ONLY the JSON object with properties - no explanations, no markdown, no e title: 'Search Query', type: 'short-input', placeholder: 'Search term (e.g., company name, contact email)', - condition: { field: 'operation', value: ['search_contacts', 'search_companies'] }, + condition: { + field: 'operation', + value: [ + 'search_contacts', + 'search_companies', + 'search_deals', + 'search_tickets', + 'get_lists', + ], + }, }, { id: 'filterGroups', @@ -293,7 +511,10 @@ Return ONLY the JSON object with properties - no explanations, no markdown, no e type: 'long-input', placeholder: 'JSON array of filter groups (e.g., [{"filters":[{"propertyName":"email","operator":"EQ","value":"test@example.com"}]}])', - condition: { field: 'operation', value: ['search_contacts', 'search_companies'] }, + condition: { + field: 'operation', + value: ['search_contacts', 'search_companies', 'search_deals', 'search_tickets'], + }, wandConfig: { enabled: true, maintainHistory: true, @@ -493,7 +714,10 @@ Return ONLY the JSON array of filter groups - no explanations, no markdown, no e type: 'long-input', placeholder: 'JSON array of sort objects (e.g., [{"propertyName":"createdate","direction":"DESCENDING"}])', - condition: { field: 'operation', value: ['search_contacts', 'search_companies'] }, + condition: { + field: 'operation', + value: ['search_contacts', 'search_companies', 'search_deals', 'search_tickets'], + }, wandConfig: { enabled: true, maintainHistory: true, @@ -614,7 +838,10 @@ Return ONLY the JSON array of sort objects - no explanations, no markdown, no ex title: 'Properties to Return', type: 'long-input', placeholder: 'JSON array of properties (e.g., ["email","firstname","lastname"])', - condition: { field: 'operation', value: ['search_contacts', 'search_companies'] }, + condition: { + field: 'operation', + value: ['search_contacts', 'search_companies', 'search_deals', 'search_tickets'], + }, wandConfig: { enabled: true, maintainHistory: true, @@ -783,6 +1010,33 @@ Return ONLY the JSON array of property names - no explanations, no markdown, no 'hubspot_update_company', 'hubspot_search_companies', 'hubspot_list_deals', + 'hubspot_get_deal', + 'hubspot_create_deal', + 'hubspot_update_deal', + 'hubspot_search_deals', + 'hubspot_list_tickets', + 'hubspot_get_ticket', + 'hubspot_create_ticket', + 'hubspot_update_ticket', + 'hubspot_search_tickets', + 'hubspot_list_line_items', + 'hubspot_get_line_item', + 'hubspot_create_line_item', + 'hubspot_update_line_item', + 'hubspot_list_quotes', + 'hubspot_get_quote', + 'hubspot_list_appointments', + 'hubspot_get_appointment', + 'hubspot_create_appointment', + 'hubspot_update_appointment', + 'hubspot_list_carts', + 'hubspot_get_cart', + 'hubspot_list_owners', + 'hubspot_list_marketing_events', + 'hubspot_get_marketing_event', + 'hubspot_list_lists', + 'hubspot_get_list', + 'hubspot_create_list', ], config: { tool: (params) => { @@ -806,7 +1060,45 @@ Return ONLY the JSON array of property names - no explanations, no markdown, no case 'search_companies': return 'hubspot_search_companies' case 'get_deals': - return 'hubspot_list_deals' + return params.dealId ? 'hubspot_get_deal' : 'hubspot_list_deals' + case 'create_deal': + return 'hubspot_create_deal' + case 'update_deal': + return 'hubspot_update_deal' + case 'search_deals': + return 'hubspot_search_deals' + case 'get_tickets': + return params.ticketId ? 'hubspot_get_ticket' : 'hubspot_list_tickets' + case 'create_ticket': + return 'hubspot_create_ticket' + case 'update_ticket': + return 'hubspot_update_ticket' + case 'search_tickets': + return 'hubspot_search_tickets' + case 'get_line_items': + return params.lineItemId ? 'hubspot_get_line_item' : 'hubspot_list_line_items' + case 'create_line_item': + return 'hubspot_create_line_item' + case 'update_line_item': + return 'hubspot_update_line_item' + case 'get_quotes': + return params.quoteId ? 'hubspot_get_quote' : 'hubspot_list_quotes' + case 'get_appointments': + return params.appointmentId ? 'hubspot_get_appointment' : 'hubspot_list_appointments' + case 'create_appointment': + return 'hubspot_create_appointment' + case 'update_appointment': + return 'hubspot_update_appointment' + case 'get_carts': + return params.cartId ? 'hubspot_get_cart' : 'hubspot_list_carts' + case 'list_owners': + return 'hubspot_list_owners' + case 'get_marketing_events': + return params.eventId ? 'hubspot_get_marketing_event' : 'hubspot_list_marketing_events' + case 'get_lists': + return params.listId ? 'hubspot_get_list' : 'hubspot_list_lists' + case 'create_list': + return 'hubspot_create_list' default: throw new Error(`Unknown operation: ${params.operation}`) } @@ -821,6 +1113,7 @@ Return ONLY the JSON array of property names - no explanations, no markdown, no filterGroups, sorts, associations, + listName, ...rest } = params @@ -833,17 +1126,34 @@ Return ONLY the JSON array of property names - no explanations, no markdown, no 'update_contact', 'create_company', 'update_company', + 'create_deal', + 'update_deal', + 'create_ticket', + 'update_ticket', + 'create_line_item', + 'update_line_item', + 'create_appointment', + 'update_appointment', ] if (propertiesToSet && createUpdateOps.includes(operation as string)) { cleanParams.properties = propertiesToSet } - const getListOps = ['get_contacts', 'get_companies', 'get_deals'] + const getListOps = [ + 'get_contacts', + 'get_companies', + 'get_deals', + 'get_tickets', + 'get_line_items', + 'get_quotes', + 'get_appointments', + 'get_carts', + ] if (properties && !searchProperties && getListOps.includes(operation as string)) { cleanParams.properties = properties } - const searchOps = ['search_contacts', 'search_companies'] + const searchOps = ['search_contacts', 'search_companies', 'search_deals', 'search_tickets'] if (searchProperties && searchOps.includes(operation as string)) { cleanParams.properties = searchProperties } @@ -856,10 +1166,34 @@ Return ONLY the JSON array of property names - no explanations, no markdown, no cleanParams.sorts = sorts } - if (associations && ['create_contact', 'create_company'].includes(operation as string)) { + const associationOps = [ + ...getListOps, + 'create_contact', + 'create_company', + 'create_deal', + 'create_ticket', + 'create_line_item', + 'create_appointment', + ] + if (associations && associationOps.includes(operation as string)) { cleanParams.associations = associations } + if (listName && operation === 'create_list') { + cleanParams.name = listName + } + + if (operation === 'get_lists') { + if (rest.limit) { + cleanParams.count = rest.limit + rest.limit = undefined + } + if (rest.after) { + cleanParams.offset = rest.after + rest.after = undefined + } + } + const excludeKeys = [ 'propertiesToSet', 'properties', @@ -867,6 +1201,7 @@ Return ONLY the JSON array of property names - no explanations, no markdown, no 'filterGroups', 'sorts', 'associations', + 'listName', ] Object.entries(rest).forEach(([key, value]) => { if (value !== undefined && value !== null && value !== '' && !excludeKeys.includes(key)) { @@ -883,6 +1218,14 @@ Return ONLY the JSON array of property names - no explanations, no markdown, no oauthCredential: { type: 'string', description: 'HubSpot access token' }, contactId: { type: 'string', description: 'Contact ID or email' }, companyId: { type: 'string', description: 'Company ID or domain' }, + dealId: { type: 'string', description: 'Deal ID' }, + ticketId: { type: 'string', description: 'Ticket ID' }, + lineItemId: { type: 'string', description: 'Line item ID' }, + quoteId: { type: 'string', description: 'Quote ID' }, + appointmentId: { type: 'string', description: 'Appointment ID' }, + cartId: { type: 'string', description: 'Cart ID' }, + eventId: { type: 'string', description: 'Marketing event ID' }, + listId: { type: 'string', description: 'List ID' }, idProperty: { type: 'string', description: 'Property name to use as unique identifier' }, propertiesToSet: { type: 'json', description: 'Properties to create/update (JSON object)' }, properties: { @@ -890,12 +1233,15 @@ Return ONLY the JSON array of property names - no explanations, no markdown, no description: 'Comma-separated properties to return (for list/get)', }, associations: { type: 'string', description: 'Comma-separated object types for associations' }, - limit: { type: 'string', description: 'Maximum results (list: 100, search: 200)' }, + limit: { type: 'string', description: 'Maximum results per page' }, after: { type: 'string', description: 'Pagination cursor' }, query: { type: 'string', description: 'Search query string' }, filterGroups: { type: 'json', description: 'Filter groups for search (JSON array)' }, sorts: { type: 'json', description: 'Sort order (JSON array of strings or objects)' }, searchProperties: { type: 'json', description: 'Properties to return in search (JSON array)' }, + listName: { type: 'string', description: 'Name for new list' }, + objectTypeId: { type: 'string', description: 'Object type ID for list' }, + processingType: { type: 'string', description: 'List processing type (MANUAL or DYNAMIC)' }, }, outputs: { users: { type: 'json', description: 'Array of user objects' }, @@ -904,6 +1250,22 @@ Return ONLY the JSON array of property names - no explanations, no markdown, no companies: { type: 'json', description: 'Array of company objects' }, company: { type: 'json', description: 'Single company object' }, deals: { type: 'json', description: 'Array of deal objects' }, + deal: { type: 'json', description: 'Single deal object' }, + tickets: { type: 'json', description: 'Array of ticket objects' }, + ticket: { type: 'json', description: 'Single ticket object' }, + lineItems: { type: 'json', description: 'Array of line item objects' }, + lineItem: { type: 'json', description: 'Single line item object' }, + quotes: { type: 'json', description: 'Array of quote objects' }, + quote: { type: 'json', description: 'Single quote object' }, + appointments: { type: 'json', description: 'Array of appointment objects' }, + appointment: { type: 'json', description: 'Single appointment object' }, + carts: { type: 'json', description: 'Array of cart objects' }, + cart: { type: 'json', description: 'Single cart object' }, + owners: { type: 'json', description: 'Array of owner objects' }, + events: { type: 'json', description: 'Array of marketing event objects' }, + event: { type: 'json', description: 'Single marketing event object' }, + lists: { type: 'json', description: 'Array of list objects' }, + list: { type: 'json', description: 'Single list object' }, total: { type: 'number', description: 'Total number of matching results (for search)' }, paging: { type: 'json', description: 'Pagination info with next/prev cursors' }, metadata: { type: 'json', description: 'Operation metadata' }, diff --git a/apps/sim/connectors/hubspot/hubspot.ts b/apps/sim/connectors/hubspot/hubspot.ts index 2461ec8ac82..0a8a669284d 100644 --- a/apps/sim/connectors/hubspot/hubspot.ts +++ b/apps/sim/connectors/hubspot/hubspot.ts @@ -185,7 +185,7 @@ export const hubspotConnector: ConnectorConfig = { 'crm.objects.contacts.read', 'crm.objects.companies.read', 'crm.objects.deals.read', - 'crm.objects.tickets.read', + 'tickets', ], }, diff --git a/apps/sim/lib/oauth/oauth.ts b/apps/sim/lib/oauth/oauth.ts index d7ba3a2cc4d..31f7d290fc4 100644 --- a/apps/sim/lib/oauth/oauth.ts +++ b/apps/sim/lib/oauth/oauth.ts @@ -886,8 +886,6 @@ export const OAUTH_PROVIDERS: Record = { 'crm.import', 'crm.lists.read', 'crm.lists.write', - 'crm.objects.tickets.read', - 'crm.objects.tickets.write', 'tickets', 'oauth', ], diff --git a/apps/sim/tools/hubspot/create_appointment.ts b/apps/sim/tools/hubspot/create_appointment.ts new file mode 100644 index 00000000000..c3d742ee028 --- /dev/null +++ b/apps/sim/tools/hubspot/create_appointment.ts @@ -0,0 +1,94 @@ +import { createLogger } from '@sim/logger' +import type { + HubSpotCreateAppointmentParams, + HubSpotCreateAppointmentResponse, +} from '@/tools/hubspot/types' +import { APPOINTMENT_OBJECT_OUTPUT } from '@/tools/hubspot/types' +import type { ToolConfig } from '@/tools/types' + +const logger = createLogger('HubSpotCreateAppointment') + +export const hubspotCreateAppointmentTool: ToolConfig< + HubSpotCreateAppointmentParams, + HubSpotCreateAppointmentResponse +> = { + id: 'hubspot_create_appointment', + name: 'Create Appointment in HubSpot', + description: 'Create a new appointment in HubSpot', + version: '1.0.0', + + oauth: { + required: true, + provider: 'hubspot', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'The access token for the HubSpot API', + }, + properties: { + type: 'object', + required: true, + visibility: 'user-or-llm', + description: + 'Appointment properties as JSON object (e.g., {"hs_meeting_title": "Discovery Call", "hs_meeting_start_time": "2024-01-15T10:00:00Z", "hs_meeting_end_time": "2024-01-15T11:00:00Z"})', + }, + associations: { + type: 'array', + required: false, + visibility: 'user-or-llm', + description: + 'Array of associations to create with the appointment as JSON. Each object should have "to.id" and "types" array with "associationCategory" and "associationTypeId"', + }, + }, + + request: { + url: () => 'https://api.hubapi.com/crm/v3/objects/appointments', + method: 'POST', + headers: (params) => { + if (!params.accessToken) { + throw new Error('Access token is required') + } + return { + Authorization: `Bearer ${params.accessToken}`, + 'Content-Type': 'application/json', + } + }, + body: (params) => { + let properties = params.properties + if (typeof properties === 'string') { + try { + properties = JSON.parse(properties) + } catch (e) { + throw new Error('Invalid JSON format for properties. Please provide a valid JSON object.') + } + } + const body: Record = { properties } + if (params.associations && params.associations.length > 0) { + body.associations = params.associations + } + return body + }, + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + if (!response.ok) { + logger.error('HubSpot API request failed', { data, status: response.status }) + throw new Error(data.message || 'Failed to create appointment in HubSpot') + } + return { + success: true, + output: { appointment: data, appointmentId: data.id, success: true }, + } + }, + + outputs: { + appointment: APPOINTMENT_OBJECT_OUTPUT, + appointmentId: { type: 'string', description: 'The created appointment ID' }, + success: { type: 'boolean', description: 'Operation success status' }, + }, +} diff --git a/apps/sim/tools/hubspot/create_deal.ts b/apps/sim/tools/hubspot/create_deal.ts new file mode 100644 index 00000000000..5781a383362 --- /dev/null +++ b/apps/sim/tools/hubspot/create_deal.ts @@ -0,0 +1,91 @@ +import { createLogger } from '@sim/logger' +import type { HubSpotCreateDealParams, HubSpotCreateDealResponse } from '@/tools/hubspot/types' +import { DEAL_OBJECT_OUTPUT } from '@/tools/hubspot/types' +import type { ToolConfig } from '@/tools/types' + +const logger = createLogger('HubSpotCreateDeal') + +export const hubspotCreateDealTool: ToolConfig = + { + id: 'hubspot_create_deal', + name: 'Create Deal in HubSpot', + description: 'Create a new deal in HubSpot. Requires at least a dealname property', + version: '1.0.0', + + oauth: { + required: true, + provider: 'hubspot', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'The access token for the HubSpot API', + }, + properties: { + type: 'object', + required: true, + visibility: 'user-or-llm', + description: + 'Deal properties as JSON object. Must include dealname (e.g., {"dealname": "New Deal", "amount": "5000", "dealstage": "appointmentscheduled"})', + }, + associations: { + type: 'array', + required: false, + visibility: 'user-or-llm', + description: + 'Array of associations to create with the deal as JSON. Each object should have "to.id" and "types" array with "associationCategory" and "associationTypeId"', + }, + }, + + request: { + url: () => 'https://api.hubapi.com/crm/v3/objects/deals', + method: 'POST', + headers: (params) => { + if (!params.accessToken) { + throw new Error('Access token is required') + } + return { + Authorization: `Bearer ${params.accessToken}`, + 'Content-Type': 'application/json', + } + }, + body: (params) => { + let properties = params.properties + if (typeof properties === 'string') { + try { + properties = JSON.parse(properties) + } catch (e) { + throw new Error( + 'Invalid JSON format for properties. Please provide a valid JSON object.' + ) + } + } + const body: Record = { properties } + if (params.associations && params.associations.length > 0) { + body.associations = params.associations + } + return body + }, + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + if (!response.ok) { + logger.error('HubSpot API request failed', { data, status: response.status }) + throw new Error(data.message || 'Failed to create deal in HubSpot') + } + return { + success: true, + output: { deal: data, dealId: data.id, success: true }, + } + }, + + outputs: { + deal: DEAL_OBJECT_OUTPUT, + dealId: { type: 'string', description: 'The created deal ID' }, + success: { type: 'boolean', description: 'Operation success status' }, + }, + } diff --git a/apps/sim/tools/hubspot/create_line_item.ts b/apps/sim/tools/hubspot/create_line_item.ts new file mode 100644 index 00000000000..ebf26c412a1 --- /dev/null +++ b/apps/sim/tools/hubspot/create_line_item.ts @@ -0,0 +1,94 @@ +import { createLogger } from '@sim/logger' +import type { + HubSpotCreateLineItemParams, + HubSpotCreateLineItemResponse, +} from '@/tools/hubspot/types' +import { LINE_ITEM_OBJECT_OUTPUT } from '@/tools/hubspot/types' +import type { ToolConfig } from '@/tools/types' + +const logger = createLogger('HubSpotCreateLineItem') + +export const hubspotCreateLineItemTool: ToolConfig< + HubSpotCreateLineItemParams, + HubSpotCreateLineItemResponse +> = { + id: 'hubspot_create_line_item', + name: 'Create Line Item in HubSpot', + description: 'Create a new line item in HubSpot. Requires at least a name property', + version: '1.0.0', + + oauth: { + required: true, + provider: 'hubspot', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'The access token for the HubSpot API', + }, + properties: { + type: 'object', + required: true, + visibility: 'user-or-llm', + description: + 'Line item properties as JSON object (e.g., {"name": "Product A", "quantity": "2", "price": "50.00", "hs_sku": "SKU-001"})', + }, + associations: { + type: 'array', + required: false, + visibility: 'user-or-llm', + description: + 'Array of associations to create with the line item as JSON. Each object should have "to.id" and "types" array with "associationCategory" and "associationTypeId"', + }, + }, + + request: { + url: () => 'https://api.hubapi.com/crm/v3/objects/line_items', + method: 'POST', + headers: (params) => { + if (!params.accessToken) { + throw new Error('Access token is required') + } + return { + Authorization: `Bearer ${params.accessToken}`, + 'Content-Type': 'application/json', + } + }, + body: (params) => { + let properties = params.properties + if (typeof properties === 'string') { + try { + properties = JSON.parse(properties) + } catch (e) { + throw new Error('Invalid JSON format for properties. Please provide a valid JSON object.') + } + } + const body: Record = { properties } + if (params.associations && params.associations.length > 0) { + body.associations = params.associations + } + return body + }, + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + if (!response.ok) { + logger.error('HubSpot API request failed', { data, status: response.status }) + throw new Error(data.message || 'Failed to create line item in HubSpot') + } + return { + success: true, + output: { lineItem: data, lineItemId: data.id, success: true }, + } + }, + + outputs: { + lineItem: LINE_ITEM_OBJECT_OUTPUT, + lineItemId: { type: 'string', description: 'The created line item ID' }, + success: { type: 'boolean', description: 'Operation success status' }, + }, +} diff --git a/apps/sim/tools/hubspot/create_list.ts b/apps/sim/tools/hubspot/create_list.ts new file mode 100644 index 00000000000..40bc6a68d2a --- /dev/null +++ b/apps/sim/tools/hubspot/create_list.ts @@ -0,0 +1,88 @@ +import { createLogger } from '@sim/logger' +import type { HubSpotCreateListParams, HubSpotCreateListResponse } from '@/tools/hubspot/types' +import { LIST_OUTPUT_PROPERTIES } from '@/tools/hubspot/types' +import type { ToolConfig } from '@/tools/types' + +const logger = createLogger('HubSpotCreateList') + +export const hubspotCreateListTool: ToolConfig = + { + id: 'hubspot_create_list', + name: 'Create List in HubSpot', + description: + 'Create a new list in HubSpot. Specify the object type and processing type (MANUAL or DYNAMIC)', + version: '1.0.0', + + oauth: { + required: true, + provider: 'hubspot', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'The access token for the HubSpot API', + }, + name: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Name of the list', + }, + objectTypeId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Object type ID (e.g., "0-1" for contacts, "0-2" for companies)', + }, + processingType: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Processing type: "MANUAL" for static lists or "DYNAMIC" for active lists', + }, + }, + + request: { + url: () => 'https://api.hubapi.com/crm/v3/lists', + method: 'POST', + headers: (params) => { + if (!params.accessToken) { + throw new Error('Access token is required') + } + return { + Authorization: `Bearer ${params.accessToken}`, + 'Content-Type': 'application/json', + } + }, + body: (params) => ({ + name: params.name, + objectTypeId: params.objectTypeId, + processingType: params.processingType, + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + if (!response.ok) { + logger.error('HubSpot API request failed', { data, status: response.status }) + throw new Error(data.message || 'Failed to create list in HubSpot') + } + return { + success: true, + output: { list: data, listId: data.listId ?? data.id, success: true }, + } + }, + + outputs: { + list: { + type: 'object', + description: 'HubSpot list', + properties: LIST_OUTPUT_PROPERTIES, + }, + listId: { type: 'string', description: 'The created list ID' }, + success: { type: 'boolean', description: 'Operation success status' }, + }, + } diff --git a/apps/sim/tools/hubspot/create_ticket.ts b/apps/sim/tools/hubspot/create_ticket.ts new file mode 100644 index 00000000000..b6ebaa1fa3f --- /dev/null +++ b/apps/sim/tools/hubspot/create_ticket.ts @@ -0,0 +1,91 @@ +import { createLogger } from '@sim/logger' +import type { HubSpotCreateTicketParams, HubSpotCreateTicketResponse } from '@/tools/hubspot/types' +import { TICKET_OBJECT_OUTPUT } from '@/tools/hubspot/types' +import type { ToolConfig } from '@/tools/types' + +const logger = createLogger('HubSpotCreateTicket') + +export const hubspotCreateTicketTool: ToolConfig< + HubSpotCreateTicketParams, + HubSpotCreateTicketResponse +> = { + id: 'hubspot_create_ticket', + name: 'Create Ticket in HubSpot', + description: 'Create a new ticket in HubSpot. Requires subject and hs_pipeline_stage properties', + version: '1.0.0', + + oauth: { + required: true, + provider: 'hubspot', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'The access token for the HubSpot API', + }, + properties: { + type: 'object', + required: true, + visibility: 'user-or-llm', + description: + 'Ticket properties as JSON object. Must include subject and hs_pipeline_stage (e.g., {"subject": "Support request", "hs_pipeline_stage": "1", "hs_ticket_priority": "HIGH"})', + }, + associations: { + type: 'array', + required: false, + visibility: 'user-or-llm', + description: + 'Array of associations to create with the ticket as JSON. Each object should have "to.id" and "types" array with "associationCategory" and "associationTypeId"', + }, + }, + + request: { + url: () => 'https://api.hubapi.com/crm/v3/objects/tickets', + method: 'POST', + headers: (params) => { + if (!params.accessToken) { + throw new Error('Access token is required') + } + return { + Authorization: `Bearer ${params.accessToken}`, + 'Content-Type': 'application/json', + } + }, + body: (params) => { + let properties = params.properties + if (typeof properties === 'string') { + try { + properties = JSON.parse(properties) + } catch (e) { + throw new Error('Invalid JSON format for properties. Please provide a valid JSON object.') + } + } + const body: Record = { properties } + if (params.associations && params.associations.length > 0) { + body.associations = params.associations + } + return body + }, + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + if (!response.ok) { + logger.error('HubSpot API request failed', { data, status: response.status }) + throw new Error(data.message || 'Failed to create ticket in HubSpot') + } + return { + success: true, + output: { ticket: data, ticketId: data.id, success: true }, + } + }, + + outputs: { + ticket: TICKET_OBJECT_OUTPUT, + ticketId: { type: 'string', description: 'The created ticket ID' }, + success: { type: 'boolean', description: 'Operation success status' }, + }, +} diff --git a/apps/sim/tools/hubspot/get_appointment.ts b/apps/sim/tools/hubspot/get_appointment.ts new file mode 100644 index 00000000000..5dceb1953d1 --- /dev/null +++ b/apps/sim/tools/hubspot/get_appointment.ts @@ -0,0 +1,99 @@ +import { createLogger } from '@sim/logger' +import type { + HubSpotGetAppointmentParams, + HubSpotGetAppointmentResponse, +} from '@/tools/hubspot/types' +import { APPOINTMENT_OBJECT_OUTPUT } from '@/tools/hubspot/types' +import type { ToolConfig } from '@/tools/types' + +const logger = createLogger('HubSpotGetAppointment') + +export const hubspotGetAppointmentTool: ToolConfig< + HubSpotGetAppointmentParams, + HubSpotGetAppointmentResponse +> = { + id: 'hubspot_get_appointment', + name: 'Get Appointment from HubSpot', + description: 'Retrieve a single appointment by ID from HubSpot', + version: '1.0.0', + + oauth: { + required: true, + provider: 'hubspot', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'The access token for the HubSpot API', + }, + appointmentId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The HubSpot appointment ID to retrieve', + }, + idProperty: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Property to use as unique identifier. If not specified, uses record ID', + }, + properties: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Comma-separated list of HubSpot property names to return (e.g., "hs_meeting_title,hs_meeting_start_time")', + }, + associations: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Comma-separated list of object types to retrieve associated IDs for (e.g., "contacts,companies")', + }, + }, + + request: { + url: (params) => { + const baseUrl = `https://api.hubapi.com/crm/v3/objects/appointments/${params.appointmentId.trim()}` + const queryParams = new URLSearchParams() + if (params.idProperty) queryParams.append('idProperty', params.idProperty) + if (params.properties) queryParams.append('properties', params.properties) + if (params.associations) queryParams.append('associations', params.associations) + const queryString = queryParams.toString() + return queryString ? `${baseUrl}?${queryString}` : baseUrl + }, + method: 'GET', + headers: (params) => { + if (!params.accessToken) { + throw new Error('Access token is required') + } + return { + Authorization: `Bearer ${params.accessToken}`, + 'Content-Type': 'application/json', + } + }, + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + if (!response.ok) { + logger.error('HubSpot API request failed', { data, status: response.status }) + throw new Error(data.message || 'Failed to get appointment from HubSpot') + } + return { + success: true, + output: { appointment: data, appointmentId: data.id, success: true }, + } + }, + + outputs: { + appointment: APPOINTMENT_OBJECT_OUTPUT, + appointmentId: { type: 'string', description: 'The retrieved appointment ID' }, + success: { type: 'boolean', description: 'Operation success status' }, + }, +} diff --git a/apps/sim/tools/hubspot/get_cart.ts b/apps/sim/tools/hubspot/get_cart.ts new file mode 100644 index 00000000000..147cfd1c7b7 --- /dev/null +++ b/apps/sim/tools/hubspot/get_cart.ts @@ -0,0 +1,84 @@ +import { createLogger } from '@sim/logger' +import type { HubSpotGetCartParams, HubSpotGetCartResponse } from '@/tools/hubspot/types' +import { GENERIC_CRM_OBJECT_OUTPUT } from '@/tools/hubspot/types' +import type { ToolConfig } from '@/tools/types' + +const logger = createLogger('HubSpotGetCart') + +export const hubspotGetCartTool: ToolConfig = { + id: 'hubspot_get_cart', + name: 'Get Cart from HubSpot', + description: 'Retrieve a single cart by ID from HubSpot', + version: '1.0.0', + + oauth: { + required: true, + provider: 'hubspot', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'The access token for the HubSpot API', + }, + cartId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The HubSpot cart ID to retrieve', + }, + properties: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated list of HubSpot property names to return', + }, + associations: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated list of object types to retrieve associated IDs for', + }, + }, + + request: { + url: (params) => { + const baseUrl = `https://api.hubapi.com/crm/v3/objects/carts/${params.cartId.trim()}` + const queryParams = new URLSearchParams() + if (params.properties) queryParams.append('properties', params.properties) + if (params.associations) queryParams.append('associations', params.associations) + const queryString = queryParams.toString() + return queryString ? `${baseUrl}?${queryString}` : baseUrl + }, + method: 'GET', + headers: (params) => { + if (!params.accessToken) { + throw new Error('Access token is required') + } + return { + Authorization: `Bearer ${params.accessToken}`, + 'Content-Type': 'application/json', + } + }, + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + if (!response.ok) { + logger.error('HubSpot API request failed', { data, status: response.status }) + throw new Error(data.message || 'Failed to get cart from HubSpot') + } + return { + success: true, + output: { cart: data, cartId: data.id, success: true }, + } + }, + + outputs: { + cart: GENERIC_CRM_OBJECT_OUTPUT, + cartId: { type: 'string', description: 'The retrieved cart ID' }, + success: { type: 'boolean', description: 'Operation success status' }, + }, +} diff --git a/apps/sim/tools/hubspot/get_company.ts b/apps/sim/tools/hubspot/get_company.ts index 74c2594a222..d1cc6cb7915 100644 --- a/apps/sim/tools/hubspot/get_company.ts +++ b/apps/sim/tools/hubspot/get_company.ts @@ -55,7 +55,7 @@ export const hubspotGetCompanyTool: ToolConfig { - const baseUrl = `https://api.hubapi.com/crm/v3/objects/companies/${params.companyId}` + const baseUrl = `https://api.hubapi.com/crm/v3/objects/companies/${params.companyId.trim()}` const queryParams = new URLSearchParams() if (params.idProperty) { diff --git a/apps/sim/tools/hubspot/get_contact.ts b/apps/sim/tools/hubspot/get_contact.ts index 42684ecd541..811c4b1ac04 100644 --- a/apps/sim/tools/hubspot/get_contact.ts +++ b/apps/sim/tools/hubspot/get_contact.ts @@ -55,7 +55,7 @@ export const hubspotGetContactTool: ToolConfig { - const baseUrl = `https://api.hubapi.com/crm/v3/objects/contacts/${params.contactId}` + const baseUrl = `https://api.hubapi.com/crm/v3/objects/contacts/${params.contactId.trim()}` const queryParams = new URLSearchParams() if (params.idProperty) { diff --git a/apps/sim/tools/hubspot/get_deal.ts b/apps/sim/tools/hubspot/get_deal.ts new file mode 100644 index 00000000000..b42dec365aa --- /dev/null +++ b/apps/sim/tools/hubspot/get_deal.ts @@ -0,0 +1,93 @@ +import { createLogger } from '@sim/logger' +import type { HubSpotGetDealParams, HubSpotGetDealResponse } from '@/tools/hubspot/types' +import { DEAL_OBJECT_OUTPUT } from '@/tools/hubspot/types' +import type { ToolConfig } from '@/tools/types' + +const logger = createLogger('HubSpotGetDeal') + +export const hubspotGetDealTool: ToolConfig = { + id: 'hubspot_get_deal', + name: 'Get Deal from HubSpot', + description: 'Retrieve a single deal by ID from HubSpot', + version: '1.0.0', + + oauth: { + required: true, + provider: 'hubspot', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'The access token for the HubSpot API', + }, + dealId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The HubSpot deal ID to retrieve', + }, + idProperty: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Property to use as unique identifier. If not specified, uses record ID', + }, + properties: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Comma-separated list of HubSpot property names to return (e.g., "dealname,amount,dealstage")', + }, + associations: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Comma-separated list of object types to retrieve associated IDs for (e.g., "contacts,companies")', + }, + }, + + request: { + url: (params) => { + const baseUrl = `https://api.hubapi.com/crm/v3/objects/deals/${params.dealId.trim()}` + const queryParams = new URLSearchParams() + if (params.idProperty) queryParams.append('idProperty', params.idProperty) + if (params.properties) queryParams.append('properties', params.properties) + if (params.associations) queryParams.append('associations', params.associations) + const queryString = queryParams.toString() + return queryString ? `${baseUrl}?${queryString}` : baseUrl + }, + method: 'GET', + headers: (params) => { + if (!params.accessToken) { + throw new Error('Access token is required') + } + return { + Authorization: `Bearer ${params.accessToken}`, + 'Content-Type': 'application/json', + } + }, + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + if (!response.ok) { + logger.error('HubSpot API request failed', { data, status: response.status }) + throw new Error(data.message || 'Failed to get deal from HubSpot') + } + return { + success: true, + output: { deal: data, dealId: data.id, success: true }, + } + }, + + outputs: { + deal: DEAL_OBJECT_OUTPUT, + dealId: { type: 'string', description: 'The retrieved deal ID' }, + success: { type: 'boolean', description: 'Operation success status' }, + }, +} diff --git a/apps/sim/tools/hubspot/get_line_item.ts b/apps/sim/tools/hubspot/get_line_item.ts new file mode 100644 index 00000000000..7bc6925a5ad --- /dev/null +++ b/apps/sim/tools/hubspot/get_line_item.ts @@ -0,0 +1,96 @@ +import { createLogger } from '@sim/logger' +import type { HubSpotGetLineItemParams, HubSpotGetLineItemResponse } from '@/tools/hubspot/types' +import { LINE_ITEM_OBJECT_OUTPUT } from '@/tools/hubspot/types' +import type { ToolConfig } from '@/tools/types' + +const logger = createLogger('HubSpotGetLineItem') + +export const hubspotGetLineItemTool: ToolConfig< + HubSpotGetLineItemParams, + HubSpotGetLineItemResponse +> = { + id: 'hubspot_get_line_item', + name: 'Get Line Item from HubSpot', + description: 'Retrieve a single line item by ID from HubSpot', + version: '1.0.0', + + oauth: { + required: true, + provider: 'hubspot', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'The access token for the HubSpot API', + }, + lineItemId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The HubSpot line item ID to retrieve', + }, + idProperty: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Property to use as unique identifier. If not specified, uses record ID', + }, + properties: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Comma-separated list of HubSpot property names to return (e.g., "name,quantity,price,amount")', + }, + associations: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Comma-separated list of object types to retrieve associated IDs for (e.g., "deals,quotes")', + }, + }, + + request: { + url: (params) => { + const baseUrl = `https://api.hubapi.com/crm/v3/objects/line_items/${params.lineItemId.trim()}` + const queryParams = new URLSearchParams() + if (params.idProperty) queryParams.append('idProperty', params.idProperty) + if (params.properties) queryParams.append('properties', params.properties) + if (params.associations) queryParams.append('associations', params.associations) + const queryString = queryParams.toString() + return queryString ? `${baseUrl}?${queryString}` : baseUrl + }, + method: 'GET', + headers: (params) => { + if (!params.accessToken) { + throw new Error('Access token is required') + } + return { + Authorization: `Bearer ${params.accessToken}`, + 'Content-Type': 'application/json', + } + }, + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + if (!response.ok) { + logger.error('HubSpot API request failed', { data, status: response.status }) + throw new Error(data.message || 'Failed to get line item from HubSpot') + } + return { + success: true, + output: { lineItem: data, lineItemId: data.id, success: true }, + } + }, + + outputs: { + lineItem: LINE_ITEM_OBJECT_OUTPUT, + lineItemId: { type: 'string', description: 'The retrieved line item ID' }, + success: { type: 'boolean', description: 'Operation success status' }, + }, +} diff --git a/apps/sim/tools/hubspot/get_list.ts b/apps/sim/tools/hubspot/get_list.ts new file mode 100644 index 00000000000..2edfd253635 --- /dev/null +++ b/apps/sim/tools/hubspot/get_list.ts @@ -0,0 +1,73 @@ +import { createLogger } from '@sim/logger' +import type { HubSpotGetListParams, HubSpotGetListResponse } from '@/tools/hubspot/types' +import { LIST_OUTPUT_PROPERTIES } from '@/tools/hubspot/types' +import type { ToolConfig } from '@/tools/types' + +const logger = createLogger('HubSpotGetList') + +export const hubspotGetListTool: ToolConfig = { + id: 'hubspot_get_list', + name: 'Get List from HubSpot', + description: 'Retrieve a single list by ID from HubSpot', + version: '1.0.0', + + oauth: { + required: true, + provider: 'hubspot', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'The access token for the HubSpot API', + }, + listId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The HubSpot list ID to retrieve', + }, + }, + + request: { + url: (params) => `https://api.hubapi.com/crm/v3/lists/${params.listId.trim()}`, + method: 'GET', + headers: (params) => { + if (!params.accessToken) { + throw new Error('Access token is required') + } + return { + Authorization: `Bearer ${params.accessToken}`, + 'Content-Type': 'application/json', + } + }, + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + if (!response.ok) { + logger.error('HubSpot API request failed', { data, status: response.status }) + throw new Error(data.message || 'Failed to get list from HubSpot') + } + return { + success: true, + output: { + list: data.list ?? data, + listId: data.list?.listId ?? data.listId ?? data.id, + success: true, + }, + } + }, + + outputs: { + list: { + type: 'object', + description: 'HubSpot list', + properties: LIST_OUTPUT_PROPERTIES, + }, + listId: { type: 'string', description: 'The retrieved list ID' }, + success: { type: 'boolean', description: 'Operation success status' }, + }, +} diff --git a/apps/sim/tools/hubspot/get_marketing_event.ts b/apps/sim/tools/hubspot/get_marketing_event.ts new file mode 100644 index 00000000000..1df13f27ebb --- /dev/null +++ b/apps/sim/tools/hubspot/get_marketing_event.ts @@ -0,0 +1,76 @@ +import { createLogger } from '@sim/logger' +import type { + HubSpotGetMarketingEventParams, + HubSpotGetMarketingEventResponse, +} from '@/tools/hubspot/types' +import { MARKETING_EVENT_OUTPUT } from '@/tools/hubspot/types' +import type { ToolConfig } from '@/tools/types' + +const logger = createLogger('HubSpotGetMarketingEvent') + +export const hubspotGetMarketingEventTool: ToolConfig< + HubSpotGetMarketingEventParams, + HubSpotGetMarketingEventResponse +> = { + id: 'hubspot_get_marketing_event', + name: 'Get Marketing Event from HubSpot', + description: 'Retrieve a single marketing event by ID from HubSpot', + version: '1.0.0', + + oauth: { + required: true, + provider: 'hubspot', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'The access token for the HubSpot API', + }, + eventId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The HubSpot marketing event objectId to retrieve', + }, + }, + + request: { + url: (params) => + `https://api.hubapi.com/marketing/v3/marketing-events/${params.eventId.trim()}`, + method: 'GET', + headers: (params) => { + if (!params.accessToken) { + throw new Error('Access token is required') + } + return { + Authorization: `Bearer ${params.accessToken}`, + 'Content-Type': 'application/json', + } + }, + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + if (!response.ok) { + logger.error('HubSpot API request failed', { data, status: response.status }) + throw new Error(data.message || 'Failed to get marketing event from HubSpot') + } + return { + success: true, + output: { + event: data, + eventId: data.objectId ?? data.id, + success: true, + }, + } + }, + + outputs: { + event: MARKETING_EVENT_OUTPUT, + eventId: { type: 'string', description: 'The retrieved marketing event ID' }, + success: { type: 'boolean', description: 'Operation success status' }, + }, +} diff --git a/apps/sim/tools/hubspot/get_quote.ts b/apps/sim/tools/hubspot/get_quote.ts new file mode 100644 index 00000000000..e02c846e947 --- /dev/null +++ b/apps/sim/tools/hubspot/get_quote.ts @@ -0,0 +1,93 @@ +import { createLogger } from '@sim/logger' +import type { HubSpotGetQuoteParams, HubSpotGetQuoteResponse } from '@/tools/hubspot/types' +import { QUOTE_OBJECT_OUTPUT } from '@/tools/hubspot/types' +import type { ToolConfig } from '@/tools/types' + +const logger = createLogger('HubSpotGetQuote') + +export const hubspotGetQuoteTool: ToolConfig = { + id: 'hubspot_get_quote', + name: 'Get Quote from HubSpot', + description: 'Retrieve a single quote by ID from HubSpot', + version: '1.0.0', + + oauth: { + required: true, + provider: 'hubspot', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'The access token for the HubSpot API', + }, + quoteId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The HubSpot quote ID to retrieve', + }, + idProperty: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Property to use as unique identifier. If not specified, uses record ID', + }, + properties: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Comma-separated list of HubSpot property names to return (e.g., "hs_title,hs_expiration_date,hs_status")', + }, + associations: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Comma-separated list of object types to retrieve associated IDs for (e.g., "deals,line_items")', + }, + }, + + request: { + url: (params) => { + const baseUrl = `https://api.hubapi.com/crm/v3/objects/quotes/${params.quoteId.trim()}` + const queryParams = new URLSearchParams() + if (params.idProperty) queryParams.append('idProperty', params.idProperty) + if (params.properties) queryParams.append('properties', params.properties) + if (params.associations) queryParams.append('associations', params.associations) + const queryString = queryParams.toString() + return queryString ? `${baseUrl}?${queryString}` : baseUrl + }, + method: 'GET', + headers: (params) => { + if (!params.accessToken) { + throw new Error('Access token is required') + } + return { + Authorization: `Bearer ${params.accessToken}`, + 'Content-Type': 'application/json', + } + }, + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + if (!response.ok) { + logger.error('HubSpot API request failed', { data, status: response.status }) + throw new Error(data.message || 'Failed to get quote from HubSpot') + } + return { + success: true, + output: { quote: data, quoteId: data.id, success: true }, + } + }, + + outputs: { + quote: QUOTE_OBJECT_OUTPUT, + quoteId: { type: 'string', description: 'The retrieved quote ID' }, + success: { type: 'boolean', description: 'Operation success status' }, + }, +} diff --git a/apps/sim/tools/hubspot/get_ticket.ts b/apps/sim/tools/hubspot/get_ticket.ts new file mode 100644 index 00000000000..4d53c722c11 --- /dev/null +++ b/apps/sim/tools/hubspot/get_ticket.ts @@ -0,0 +1,93 @@ +import { createLogger } from '@sim/logger' +import type { HubSpotGetTicketParams, HubSpotGetTicketResponse } from '@/tools/hubspot/types' +import { TICKET_OBJECT_OUTPUT } from '@/tools/hubspot/types' +import type { ToolConfig } from '@/tools/types' + +const logger = createLogger('HubSpotGetTicket') + +export const hubspotGetTicketTool: ToolConfig = { + id: 'hubspot_get_ticket', + name: 'Get Ticket from HubSpot', + description: 'Retrieve a single ticket by ID from HubSpot', + version: '1.0.0', + + oauth: { + required: true, + provider: 'hubspot', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'The access token for the HubSpot API', + }, + ticketId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The HubSpot ticket ID to retrieve', + }, + idProperty: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Property to use as unique identifier. If not specified, uses record ID', + }, + properties: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Comma-separated list of HubSpot property names to return (e.g., "subject,content,hs_ticket_priority")', + }, + associations: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Comma-separated list of object types to retrieve associated IDs for (e.g., "contacts,companies")', + }, + }, + + request: { + url: (params) => { + const baseUrl = `https://api.hubapi.com/crm/v3/objects/tickets/${params.ticketId.trim()}` + const queryParams = new URLSearchParams() + if (params.idProperty) queryParams.append('idProperty', params.idProperty) + if (params.properties) queryParams.append('properties', params.properties) + if (params.associations) queryParams.append('associations', params.associations) + const queryString = queryParams.toString() + return queryString ? `${baseUrl}?${queryString}` : baseUrl + }, + method: 'GET', + headers: (params) => { + if (!params.accessToken) { + throw new Error('Access token is required') + } + return { + Authorization: `Bearer ${params.accessToken}`, + 'Content-Type': 'application/json', + } + }, + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + if (!response.ok) { + logger.error('HubSpot API request failed', { data, status: response.status }) + throw new Error(data.message || 'Failed to get ticket from HubSpot') + } + return { + success: true, + output: { ticket: data, ticketId: data.id, success: true }, + } + }, + + outputs: { + ticket: TICKET_OBJECT_OUTPUT, + ticketId: { type: 'string', description: 'The retrieved ticket ID' }, + success: { type: 'boolean', description: 'Operation success status' }, + }, +} diff --git a/apps/sim/tools/hubspot/get_users.ts b/apps/sim/tools/hubspot/get_users.ts index ad9ed869655..5a23a6d2c41 100644 --- a/apps/sim/tools/hubspot/get_users.ts +++ b/apps/sim/tools/hubspot/get_users.ts @@ -1,6 +1,6 @@ import { createLogger } from '@sim/logger' import type { HubSpotGetUsersParams, HubSpotGetUsersResponse } from '@/tools/hubspot/types' -import { USERS_ARRAY_OUTPUT } from '@/tools/hubspot/types' +import { PAGING_OUTPUT, USERS_ARRAY_OUTPUT } from '@/tools/hubspot/types' import type { ToolConfig } from '@/tools/types' const logger = createLogger('HubSpotGetUsers') @@ -29,6 +29,12 @@ export const hubspotGetUsersTool: ToolConfig { + transformResponse: async (response: Response) => { const data = await response.json() if (!response.ok) { @@ -70,6 +79,7 @@ export const hubspotGetUsersTool: ToolConfig = { + id: 'hubspot_list_appointments', + name: 'List Appointments from HubSpot', + description: 'Retrieve all appointments from HubSpot account with pagination support', + version: '1.0.0', + + oauth: { + required: true, + provider: 'hubspot', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'The access token for the HubSpot API', + }, + limit: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of results per page (max 100, default 10)', + }, + after: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Pagination cursor for next page of results (from previous response)', + }, + properties: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Comma-separated list of HubSpot property names to return (e.g., "hs_meeting_title,hs_meeting_start_time")', + }, + associations: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Comma-separated list of object types to retrieve associated IDs for (e.g., "contacts,companies")', + }, + }, + + request: { + url: (params) => { + const baseUrl = 'https://api.hubapi.com/crm/v3/objects/appointments' + const queryParams = new URLSearchParams() + if (params.limit) queryParams.append('limit', params.limit) + if (params.after) queryParams.append('after', params.after) + if (params.properties) queryParams.append('properties', params.properties) + if (params.associations) queryParams.append('associations', params.associations) + const queryString = queryParams.toString() + return queryString ? `${baseUrl}?${queryString}` : baseUrl + }, + method: 'GET', + headers: (params) => { + if (!params.accessToken) { + throw new Error('Access token is required') + } + return { + Authorization: `Bearer ${params.accessToken}`, + 'Content-Type': 'application/json', + } + }, + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + if (!response.ok) { + logger.error('HubSpot API request failed', { data, status: response.status }) + throw new Error(data.message || 'Failed to list appointments from HubSpot') + } + return { + success: true, + output: { + appointments: data.results || [], + paging: data.paging ?? null, + metadata: { + totalReturned: data.results?.length || 0, + hasMore: !!data.paging?.next, + }, + success: true, + }, + } + }, + + outputs: { + appointments: APPOINTMENTS_ARRAY_OUTPUT, + paging: PAGING_OUTPUT, + metadata: METADATA_OUTPUT, + success: { type: 'boolean', description: 'Operation success status' }, + }, +} diff --git a/apps/sim/tools/hubspot/list_carts.ts b/apps/sim/tools/hubspot/list_carts.ts new file mode 100644 index 00000000000..14a6f75bb92 --- /dev/null +++ b/apps/sim/tools/hubspot/list_carts.ts @@ -0,0 +1,101 @@ +import { createLogger } from '@sim/logger' +import type { HubSpotListCartsParams, HubSpotListCartsResponse } from '@/tools/hubspot/types' +import { GENERIC_CRM_ARRAY_OUTPUT, METADATA_OUTPUT, PAGING_OUTPUT } from '@/tools/hubspot/types' +import type { ToolConfig } from '@/tools/types' + +const logger = createLogger('HubSpotListCarts') + +export const hubspotListCartsTool: ToolConfig = { + id: 'hubspot_list_carts', + name: 'List Carts from HubSpot', + description: 'Retrieve all carts from HubSpot account with pagination support', + version: '1.0.0', + + oauth: { + required: true, + provider: 'hubspot', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'The access token for the HubSpot API', + }, + limit: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of results per page (max 100, default 10)', + }, + after: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Pagination cursor for next page of results (from previous response)', + }, + properties: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated list of HubSpot property names to return', + }, + associations: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated list of object types to retrieve associated IDs for', + }, + }, + + request: { + url: (params) => { + const baseUrl = 'https://api.hubapi.com/crm/v3/objects/carts' + const queryParams = new URLSearchParams() + if (params.limit) queryParams.append('limit', params.limit) + if (params.after) queryParams.append('after', params.after) + if (params.properties) queryParams.append('properties', params.properties) + if (params.associations) queryParams.append('associations', params.associations) + const queryString = queryParams.toString() + return queryString ? `${baseUrl}?${queryString}` : baseUrl + }, + method: 'GET', + headers: (params) => { + if (!params.accessToken) { + throw new Error('Access token is required') + } + return { + Authorization: `Bearer ${params.accessToken}`, + 'Content-Type': 'application/json', + } + }, + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + if (!response.ok) { + logger.error('HubSpot API request failed', { data, status: response.status }) + throw new Error(data.message || 'Failed to list carts from HubSpot') + } + return { + success: true, + output: { + carts: data.results || [], + paging: data.paging ?? null, + metadata: { + totalReturned: data.results?.length || 0, + hasMore: !!data.paging?.next, + }, + success: true, + }, + } + }, + + outputs: { + carts: GENERIC_CRM_ARRAY_OUTPUT, + paging: PAGING_OUTPUT, + metadata: METADATA_OUTPUT, + success: { type: 'boolean', description: 'Operation success status' }, + }, +} diff --git a/apps/sim/tools/hubspot/list_deals.ts b/apps/sim/tools/hubspot/list_deals.ts index b661de80f2e..1b4f146222d 100644 --- a/apps/sim/tools/hubspot/list_deals.ts +++ b/apps/sim/tools/hubspot/list_deals.ts @@ -27,7 +27,7 @@ export const hubspotListDealsTool: ToolConfig = { + id: 'hubspot_list_line_items', + name: 'List Line Items from HubSpot', + description: 'Retrieve all line items from HubSpot account with pagination support', + version: '1.0.0', + + oauth: { + required: true, + provider: 'hubspot', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'The access token for the HubSpot API', + }, + limit: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of results per page (max 100, default 10)', + }, + after: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Pagination cursor for next page of results (from previous response)', + }, + properties: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Comma-separated list of HubSpot property names to return (e.g., "name,quantity,price,amount")', + }, + associations: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Comma-separated list of object types to retrieve associated IDs for (e.g., "deals,quotes")', + }, + }, + + request: { + url: (params) => { + const baseUrl = 'https://api.hubapi.com/crm/v3/objects/line_items' + const queryParams = new URLSearchParams() + if (params.limit) queryParams.append('limit', params.limit) + if (params.after) queryParams.append('after', params.after) + if (params.properties) queryParams.append('properties', params.properties) + if (params.associations) queryParams.append('associations', params.associations) + const queryString = queryParams.toString() + return queryString ? `${baseUrl}?${queryString}` : baseUrl + }, + method: 'GET', + headers: (params) => { + if (!params.accessToken) { + throw new Error('Access token is required') + } + return { + Authorization: `Bearer ${params.accessToken}`, + 'Content-Type': 'application/json', + } + }, + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + if (!response.ok) { + logger.error('HubSpot API request failed', { data, status: response.status }) + throw new Error(data.message || 'Failed to list line items from HubSpot') + } + return { + success: true, + output: { + lineItems: data.results || [], + paging: data.paging ?? null, + metadata: { + totalReturned: data.results?.length || 0, + hasMore: !!data.paging?.next, + }, + success: true, + }, + } + }, + + outputs: { + lineItems: LINE_ITEMS_ARRAY_OUTPUT, + paging: PAGING_OUTPUT, + metadata: METADATA_OUTPUT, + success: { type: 'boolean', description: 'Operation success status' }, + }, +} diff --git a/apps/sim/tools/hubspot/list_lists.ts b/apps/sim/tools/hubspot/list_lists.ts new file mode 100644 index 00000000000..7fa3fbabb52 --- /dev/null +++ b/apps/sim/tools/hubspot/list_lists.ts @@ -0,0 +1,107 @@ +import { createLogger } from '@sim/logger' +import type { HubSpotListListsParams, HubSpotListListsResponse } from '@/tools/hubspot/types' +import { LISTS_ARRAY_OUTPUT, METADATA_OUTPUT_PROPERTIES, PAGING_OUTPUT } from '@/tools/hubspot/types' +import type { ToolConfig } from '@/tools/types' + +const logger = createLogger('HubSpotListLists') + +export const hubspotListListsTool: ToolConfig = { + id: 'hubspot_list_lists', + name: 'List Lists from HubSpot', + description: 'Search and retrieve lists from HubSpot account', + version: '1.0.0', + + oauth: { + required: true, + provider: 'hubspot', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'The access token for the HubSpot API', + }, + query: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Search query to filter lists by name. Leave empty to return all lists.', + }, + count: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of results to return (default 20, max 500)', + }, + offset: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Pagination offset for next page of results (use the offset value from previous response)', + }, + }, + + request: { + url: () => 'https://api.hubapi.com/crm/v3/lists/search', + method: 'POST', + headers: (params) => { + if (!params.accessToken) { + throw new Error('Access token is required') + } + return { + Authorization: `Bearer ${params.accessToken}`, + 'Content-Type': 'application/json', + } + }, + body: (params) => { + const body: Record = { + offset: params.offset ? Number(params.offset) : 0, + } + if (params.query) body.query = params.query + if (params.count) body.count = Number(params.count) + return body + }, + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + if (!response.ok) { + logger.error('HubSpot API request failed', { data, status: response.status }) + throw new Error(data.message || 'Failed to list lists from HubSpot') + } + const lists = data.lists ?? [] + return { + success: true, + output: { + lists, + paging: + data.hasMore === true && data.offset != null + ? { next: { after: String(data.offset) } } + : undefined, + metadata: { + totalReturned: lists.length, + total: data.total ?? null, + hasMore: data.hasMore === true, + }, + success: true, + }, + } + }, + + outputs: { + lists: LISTS_ARRAY_OUTPUT, + paging: PAGING_OUTPUT, + metadata: { + type: 'object', + description: 'Response metadata', + properties: { + ...METADATA_OUTPUT_PROPERTIES, + total: { type: 'number', description: 'Total number of lists matching the query', optional: true }, + }, + }, + success: { type: 'boolean', description: 'Operation success status' }, + }, +} diff --git a/apps/sim/tools/hubspot/list_marketing_events.ts b/apps/sim/tools/hubspot/list_marketing_events.ts new file mode 100644 index 00000000000..2d77bec60ec --- /dev/null +++ b/apps/sim/tools/hubspot/list_marketing_events.ts @@ -0,0 +1,98 @@ +import { createLogger } from '@sim/logger' +import type { + HubSpotListMarketingEventsParams, + HubSpotListMarketingEventsResponse, +} from '@/tools/hubspot/types' +import { + MARKETING_EVENTS_ARRAY_OUTPUT, + METADATA_OUTPUT, + PAGING_OUTPUT, +} from '@/tools/hubspot/types' +import type { ToolConfig } from '@/tools/types' + +const logger = createLogger('HubSpotListMarketingEvents') + +export const hubspotListMarketingEventsTool: ToolConfig< + HubSpotListMarketingEventsParams, + HubSpotListMarketingEventsResponse +> = { + id: 'hubspot_list_marketing_events', + name: 'List Marketing Events from HubSpot', + description: 'Retrieve all marketing events from HubSpot account with pagination support', + version: '1.0.0', + + oauth: { + required: true, + provider: 'hubspot', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'The access token for the HubSpot API', + }, + limit: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of results per page (max 100, default 10)', + }, + after: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Pagination cursor for next page of results (from previous response)', + }, + }, + + request: { + url: (params) => { + const baseUrl = 'https://api.hubapi.com/marketing/v3/marketing-events' + const queryParams = new URLSearchParams() + if (params.limit) queryParams.append('limit', params.limit) + if (params.after) queryParams.append('after', params.after) + const queryString = queryParams.toString() + return queryString ? `${baseUrl}?${queryString}` : baseUrl + }, + method: 'GET', + headers: (params) => { + if (!params.accessToken) { + throw new Error('Access token is required') + } + return { + Authorization: `Bearer ${params.accessToken}`, + 'Content-Type': 'application/json', + } + }, + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + if (!response.ok) { + logger.error('HubSpot API request failed', { data, status: response.status }) + throw new Error(data.message || 'Failed to list marketing events from HubSpot') + } + const results = data.results || [] + return { + success: true, + output: { + events: results, + paging: data.paging ?? null, + metadata: { + totalReturned: results.length, + hasMore: !!data.paging?.next, + }, + success: true, + }, + } + }, + + outputs: { + events: MARKETING_EVENTS_ARRAY_OUTPUT, + paging: PAGING_OUTPUT, + metadata: METADATA_OUTPUT, + success: { type: 'boolean', description: 'Operation success status' }, + }, +} diff --git a/apps/sim/tools/hubspot/list_owners.ts b/apps/sim/tools/hubspot/list_owners.ts new file mode 100644 index 00000000000..c7f2c6cf6e6 --- /dev/null +++ b/apps/sim/tools/hubspot/list_owners.ts @@ -0,0 +1,95 @@ +import { createLogger } from '@sim/logger' +import type { HubSpotListOwnersParams, HubSpotListOwnersResponse } from '@/tools/hubspot/types' +import { METADATA_OUTPUT, OWNERS_ARRAY_OUTPUT, PAGING_OUTPUT } from '@/tools/hubspot/types' +import type { ToolConfig } from '@/tools/types' + +const logger = createLogger('HubSpotListOwners') + +export const hubspotListOwnersTool: ToolConfig = + { + id: 'hubspot_list_owners', + name: 'List Owners from HubSpot', + description: 'Retrieve all owners from HubSpot account with pagination support', + version: '1.0.0', + + oauth: { + required: true, + provider: 'hubspot', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'The access token for the HubSpot API', + }, + limit: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of results per page (max 100, default 100)', + }, + after: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Pagination cursor for next page of results (from previous response)', + }, + email: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter owners by email address', + }, + }, + + request: { + url: (params) => { + const baseUrl = 'https://api.hubapi.com/crm/v3/owners' + const queryParams = new URLSearchParams() + if (params.limit) queryParams.append('limit', params.limit) + if (params.after) queryParams.append('after', params.after) + if (params.email) queryParams.append('email', params.email) + const queryString = queryParams.toString() + return queryString ? `${baseUrl}?${queryString}` : baseUrl + }, + method: 'GET', + headers: (params) => { + if (!params.accessToken) { + throw new Error('Access token is required') + } + return { + Authorization: `Bearer ${params.accessToken}`, + 'Content-Type': 'application/json', + } + }, + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + if (!response.ok) { + logger.error('HubSpot API request failed', { data, status: response.status }) + throw new Error(data.message || 'Failed to list owners from HubSpot') + } + return { + success: true, + output: { + owners: data.results || [], + paging: data.paging ?? null, + metadata: { + totalReturned: data.results?.length || 0, + hasMore: !!data.paging?.next, + }, + success: true, + }, + } + }, + + outputs: { + owners: OWNERS_ARRAY_OUTPUT, + paging: PAGING_OUTPUT, + metadata: METADATA_OUTPUT, + success: { type: 'boolean', description: 'Operation success status' }, + }, + } diff --git a/apps/sim/tools/hubspot/list_quotes.ts b/apps/sim/tools/hubspot/list_quotes.ts new file mode 100644 index 00000000000..4983a47b230 --- /dev/null +++ b/apps/sim/tools/hubspot/list_quotes.ts @@ -0,0 +1,104 @@ +import { createLogger } from '@sim/logger' +import type { HubSpotListQuotesParams, HubSpotListQuotesResponse } from '@/tools/hubspot/types' +import { METADATA_OUTPUT, PAGING_OUTPUT, QUOTES_ARRAY_OUTPUT } from '@/tools/hubspot/types' +import type { ToolConfig } from '@/tools/types' + +const logger = createLogger('HubSpotListQuotes') + +export const hubspotListQuotesTool: ToolConfig = + { + id: 'hubspot_list_quotes', + name: 'List Quotes from HubSpot', + description: 'Retrieve all quotes from HubSpot account with pagination support', + version: '1.0.0', + + oauth: { + required: true, + provider: 'hubspot', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'The access token for the HubSpot API', + }, + limit: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of results per page (max 100, default 10)', + }, + after: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Pagination cursor for next page of results (from previous response)', + }, + properties: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Comma-separated list of HubSpot property names to return (e.g., "hs_title,hs_expiration_date,hs_status")', + }, + associations: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Comma-separated list of object types to retrieve associated IDs for (e.g., "deals,line_items")', + }, + }, + + request: { + url: (params) => { + const baseUrl = 'https://api.hubapi.com/crm/v3/objects/quotes' + const queryParams = new URLSearchParams() + if (params.limit) queryParams.append('limit', params.limit) + if (params.after) queryParams.append('after', params.after) + if (params.properties) queryParams.append('properties', params.properties) + if (params.associations) queryParams.append('associations', params.associations) + const queryString = queryParams.toString() + return queryString ? `${baseUrl}?${queryString}` : baseUrl + }, + method: 'GET', + headers: (params) => { + if (!params.accessToken) { + throw new Error('Access token is required') + } + return { + Authorization: `Bearer ${params.accessToken}`, + 'Content-Type': 'application/json', + } + }, + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + if (!response.ok) { + logger.error('HubSpot API request failed', { data, status: response.status }) + throw new Error(data.message || 'Failed to list quotes from HubSpot') + } + return { + success: true, + output: { + quotes: data.results || [], + paging: data.paging ?? null, + metadata: { + totalReturned: data.results?.length || 0, + hasMore: !!data.paging?.next, + }, + success: true, + }, + } + }, + + outputs: { + quotes: QUOTES_ARRAY_OUTPUT, + paging: PAGING_OUTPUT, + metadata: METADATA_OUTPUT, + success: { type: 'boolean', description: 'Operation success status' }, + }, + } diff --git a/apps/sim/tools/hubspot/list_tickets.ts b/apps/sim/tools/hubspot/list_tickets.ts new file mode 100644 index 00000000000..098af1d0888 --- /dev/null +++ b/apps/sim/tools/hubspot/list_tickets.ts @@ -0,0 +1,106 @@ +import { createLogger } from '@sim/logger' +import type { HubSpotListTicketsParams, HubSpotListTicketsResponse } from '@/tools/hubspot/types' +import { METADATA_OUTPUT, PAGING_OUTPUT, TICKETS_ARRAY_OUTPUT } from '@/tools/hubspot/types' +import type { ToolConfig } from '@/tools/types' + +const logger = createLogger('HubSpotListTickets') + +export const hubspotListTicketsTool: ToolConfig< + HubSpotListTicketsParams, + HubSpotListTicketsResponse +> = { + id: 'hubspot_list_tickets', + name: 'List Tickets from HubSpot', + description: 'Retrieve all tickets from HubSpot account with pagination support', + version: '1.0.0', + + oauth: { + required: true, + provider: 'hubspot', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'The access token for the HubSpot API', + }, + limit: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of results per page (max 100, default 10)', + }, + after: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Pagination cursor for next page of results (from previous response)', + }, + properties: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Comma-separated list of HubSpot property names to return (e.g., "subject,content,hs_ticket_priority")', + }, + associations: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Comma-separated list of object types to retrieve associated IDs for (e.g., "contacts,companies")', + }, + }, + + request: { + url: (params) => { + const baseUrl = 'https://api.hubapi.com/crm/v3/objects/tickets' + const queryParams = new URLSearchParams() + if (params.limit) queryParams.append('limit', params.limit) + if (params.after) queryParams.append('after', params.after) + if (params.properties) queryParams.append('properties', params.properties) + if (params.associations) queryParams.append('associations', params.associations) + const queryString = queryParams.toString() + return queryString ? `${baseUrl}?${queryString}` : baseUrl + }, + method: 'GET', + headers: (params) => { + if (!params.accessToken) { + throw new Error('Access token is required') + } + return { + Authorization: `Bearer ${params.accessToken}`, + 'Content-Type': 'application/json', + } + }, + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + if (!response.ok) { + logger.error('HubSpot API request failed', { data, status: response.status }) + throw new Error(data.message || 'Failed to list tickets from HubSpot') + } + return { + success: true, + output: { + tickets: data.results || [], + paging: data.paging ?? null, + metadata: { + totalReturned: data.results?.length || 0, + hasMore: !!data.paging?.next, + }, + success: true, + }, + } + }, + + outputs: { + tickets: TICKETS_ARRAY_OUTPUT, + paging: PAGING_OUTPUT, + metadata: METADATA_OUTPUT, + success: { type: 'boolean', description: 'Operation success status' }, + }, +} diff --git a/apps/sim/tools/hubspot/search_companies.ts b/apps/sim/tools/hubspot/search_companies.ts index ec2f92457a8..e05ddb8c169 100644 --- a/apps/sim/tools/hubspot/search_companies.ts +++ b/apps/sim/tools/hubspot/search_companies.ts @@ -34,7 +34,7 @@ export const hubspotSearchCompaniesTool: ToolConfig< required: false, visibility: 'user-or-llm', description: - 'Array of filter groups as JSON. Each group contains "filters" array with objects having "propertyName", "operator" (e.g., "EQ", "CONTAINS"), and "value"', + 'Array of filter groups as JSON. Each group contains "filters" array with objects having "propertyName", "operator" (e.g., "EQ", "CONTAINS_TOKEN", "GT"), and "value"', }, sorts: { type: 'array', @@ -152,7 +152,7 @@ export const hubspotSearchCompaniesTool: ToolConfig< success: true, output: { companies: data.results || [], - total: data.total ?? null, + total: data.total ?? 0, paging: data.paging ?? null, metadata: { totalReturned: data.results?.length || 0, diff --git a/apps/sim/tools/hubspot/search_contacts.ts b/apps/sim/tools/hubspot/search_contacts.ts index f7b375bd544..44fd75f2687 100644 --- a/apps/sim/tools/hubspot/search_contacts.ts +++ b/apps/sim/tools/hubspot/search_contacts.ts @@ -34,7 +34,7 @@ export const hubspotSearchContactsTool: ToolConfig< required: false, visibility: 'user-or-llm', description: - 'Array of filter groups as JSON. Each group contains "filters" array with objects having "propertyName", "operator" (e.g., "EQ", "CONTAINS"), and "value"', + 'Array of filter groups as JSON. Each group contains "filters" array with objects having "propertyName", "operator" (e.g., "EQ", "CONTAINS_TOKEN", "GT"), and "value"', }, sorts: { type: 'array', @@ -152,7 +152,7 @@ export const hubspotSearchContactsTool: ToolConfig< success: true, output: { contacts: data.results || [], - total: data.total ?? null, + total: data.total ?? 0, paging: data.paging ?? null, metadata: { totalReturned: data.results?.length || 0, diff --git a/apps/sim/tools/hubspot/search_deals.ts b/apps/sim/tools/hubspot/search_deals.ts new file mode 100644 index 00000000000..873aa52d4cb --- /dev/null +++ b/apps/sim/tools/hubspot/search_deals.ts @@ -0,0 +1,152 @@ +import { createLogger } from '@sim/logger' +import type { HubSpotSearchDealsParams, HubSpotSearchDealsResponse } from '@/tools/hubspot/types' +import { DEALS_ARRAY_OUTPUT, METADATA_OUTPUT, PAGING_OUTPUT } from '@/tools/hubspot/types' +import type { ToolConfig } from '@/tools/types' + +const logger = createLogger('HubSpotSearchDeals') + +export const hubspotSearchDealsTool: ToolConfig< + HubSpotSearchDealsParams, + HubSpotSearchDealsResponse +> = { + id: 'hubspot_search_deals', + name: 'Search Deals in HubSpot', + description: 'Search for deals in HubSpot using filters, sorting, and queries', + version: '1.0.0', + + oauth: { + required: true, + provider: 'hubspot', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'The access token for the HubSpot API', + }, + filterGroups: { + type: 'array', + required: false, + visibility: 'user-or-llm', + description: + 'Array of filter groups as JSON. Each group contains "filters" array with objects having "propertyName", "operator" (e.g., "EQ", "NEQ", "CONTAINS_TOKEN", "NOT_CONTAINS_TOKEN"), and "value"', + }, + sorts: { + type: 'array', + required: false, + visibility: 'user-or-llm', + description: + 'Array of sort objects as JSON with "propertyName" and "direction" ("ASCENDING" or "DESCENDING")', + }, + query: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Search query string to match against deal name and other text fields', + }, + properties: { + type: 'array', + required: false, + visibility: 'user-or-llm', + description: + 'Array of HubSpot property names to return (e.g., ["dealname", "amount", "dealstage"])', + }, + limit: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of results to return (max 200)', + }, + after: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Pagination cursor for next page (from previous response)', + }, + }, + + request: { + url: () => 'https://api.hubapi.com/crm/v3/objects/deals/search', + method: 'POST', + headers: (params) => { + if (!params.accessToken) { + throw new Error('Access token is required') + } + return { + Authorization: `Bearer ${params.accessToken}`, + 'Content-Type': 'application/json', + } + }, + body: (params) => { + const body: Record = {} + if (params.filterGroups) { + let parsed = params.filterGroups + if (typeof parsed === 'string') { + try { + parsed = JSON.parse(parsed) + } catch (e) { + throw new Error(`Invalid JSON for filterGroups: ${(e as Error).message}`) + } + } + if (Array.isArray(parsed) && parsed.length > 0) body.filterGroups = parsed + } + if (params.sorts) { + let parsed = params.sorts + if (typeof parsed === 'string') { + try { + parsed = JSON.parse(parsed) + } catch (e) { + throw new Error(`Invalid JSON for sorts: ${(e as Error).message}`) + } + } + if (Array.isArray(parsed) && parsed.length > 0) body.sorts = parsed + } + if (params.query) body.query = params.query + if (params.properties) { + let parsed = params.properties + if (typeof parsed === 'string') { + try { + parsed = JSON.parse(parsed) + } catch (e) { + throw new Error(`Invalid JSON for properties: ${(e as Error).message}`) + } + } + if (Array.isArray(parsed) && parsed.length > 0) body.properties = parsed + } + if (params.limit) body.limit = params.limit + if (params.after) body.after = params.after + return body + }, + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + if (!response.ok) { + logger.error('HubSpot API request failed', { data, status: response.status }) + throw new Error(data.message || 'Failed to search deals in HubSpot') + } + return { + success: true, + output: { + deals: data.results || [], + total: data.total ?? 0, + paging: data.paging ?? null, + metadata: { + totalReturned: data.results?.length || 0, + hasMore: !!data.paging?.next, + }, + success: true, + }, + } + }, + + outputs: { + deals: DEALS_ARRAY_OUTPUT, + total: { type: 'number', description: 'Total number of matching deals', optional: true }, + paging: PAGING_OUTPUT, + metadata: METADATA_OUTPUT, + success: { type: 'boolean', description: 'Operation success status' }, + }, +} diff --git a/apps/sim/tools/hubspot/search_tickets.ts b/apps/sim/tools/hubspot/search_tickets.ts new file mode 100644 index 00000000000..f7d7530f7d4 --- /dev/null +++ b/apps/sim/tools/hubspot/search_tickets.ts @@ -0,0 +1,155 @@ +import { createLogger } from '@sim/logger' +import type { + HubSpotSearchTicketsParams, + HubSpotSearchTicketsResponse, +} from '@/tools/hubspot/types' +import { METADATA_OUTPUT, PAGING_OUTPUT, TICKETS_ARRAY_OUTPUT } from '@/tools/hubspot/types' +import type { ToolConfig } from '@/tools/types' + +const logger = createLogger('HubSpotSearchTickets') + +export const hubspotSearchTicketsTool: ToolConfig< + HubSpotSearchTicketsParams, + HubSpotSearchTicketsResponse +> = { + id: 'hubspot_search_tickets', + name: 'Search Tickets in HubSpot', + description: 'Search for tickets in HubSpot using filters, sorting, and queries', + version: '1.0.0', + + oauth: { + required: true, + provider: 'hubspot', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'The access token for the HubSpot API', + }, + filterGroups: { + type: 'array', + required: false, + visibility: 'user-or-llm', + description: + 'Array of filter groups as JSON. Each group contains "filters" array with objects having "propertyName", "operator" (e.g., "EQ", "NEQ", "CONTAINS_TOKEN", "NOT_CONTAINS_TOKEN"), and "value"', + }, + sorts: { + type: 'array', + required: false, + visibility: 'user-or-llm', + description: + 'Array of sort objects as JSON with "propertyName" and "direction" ("ASCENDING" or "DESCENDING")', + }, + query: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Search query string to match against ticket subject and other text fields', + }, + properties: { + type: 'array', + required: false, + visibility: 'user-or-llm', + description: + 'Array of HubSpot property names to return (e.g., ["subject", "content", "hs_ticket_priority"])', + }, + limit: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of results to return (max 200)', + }, + after: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Pagination cursor for next page (from previous response)', + }, + }, + + request: { + url: () => 'https://api.hubapi.com/crm/v3/objects/tickets/search', + method: 'POST', + headers: (params) => { + if (!params.accessToken) { + throw new Error('Access token is required') + } + return { + Authorization: `Bearer ${params.accessToken}`, + 'Content-Type': 'application/json', + } + }, + body: (params) => { + const body: Record = {} + if (params.filterGroups) { + let parsed = params.filterGroups + if (typeof parsed === 'string') { + try { + parsed = JSON.parse(parsed) + } catch (e) { + throw new Error(`Invalid JSON for filterGroups: ${(e as Error).message}`) + } + } + if (Array.isArray(parsed) && parsed.length > 0) body.filterGroups = parsed + } + if (params.sorts) { + let parsed = params.sorts + if (typeof parsed === 'string') { + try { + parsed = JSON.parse(parsed) + } catch (e) { + throw new Error(`Invalid JSON for sorts: ${(e as Error).message}`) + } + } + if (Array.isArray(parsed) && parsed.length > 0) body.sorts = parsed + } + if (params.query) body.query = params.query + if (params.properties) { + let parsed = params.properties + if (typeof parsed === 'string') { + try { + parsed = JSON.parse(parsed) + } catch (e) { + throw new Error(`Invalid JSON for properties: ${(e as Error).message}`) + } + } + if (Array.isArray(parsed) && parsed.length > 0) body.properties = parsed + } + if (params.limit) body.limit = params.limit + if (params.after) body.after = params.after + return body + }, + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + if (!response.ok) { + logger.error('HubSpot API request failed', { data, status: response.status }) + throw new Error(data.message || 'Failed to search tickets in HubSpot') + } + return { + success: true, + output: { + tickets: data.results || [], + total: data.total ?? 0, + paging: data.paging ?? null, + metadata: { + totalReturned: data.results?.length || 0, + hasMore: !!data.paging?.next, + }, + success: true, + }, + } + }, + + outputs: { + tickets: TICKETS_ARRAY_OUTPUT, + total: { type: 'number', description: 'Total number of matching tickets', optional: true }, + paging: PAGING_OUTPUT, + metadata: METADATA_OUTPUT, + success: { type: 'boolean', description: 'Operation success status' }, + }, +} diff --git a/apps/sim/tools/hubspot/types.ts b/apps/sim/tools/hubspot/types.ts index 89fd757117d..ba0bd5e4e53 100644 --- a/apps/sim/tools/hubspot/types.ts +++ b/apps/sim/tools/hubspot/types.ts @@ -291,6 +291,394 @@ export const DEALS_ARRAY_OUTPUT: OutputProperty = { }, } +/** + * Common ticket properties returned by HubSpot API. + * Default properties: subject, content, hs_pipeline, hs_pipeline_stage, hs_ticket_priority, hs_ticket_category. + * @see https://developers.hubspot.com/docs/api/crm/tickets + */ +export const TICKET_PROPERTIES_OUTPUT = { + subject: { type: 'string', description: 'Ticket subject/name' }, + content: { type: 'string', description: 'Ticket content/description' }, + hs_pipeline: { type: 'string', description: 'Pipeline the ticket is in' }, + hs_pipeline_stage: { type: 'string', description: 'Current pipeline stage' }, + hs_ticket_priority: { type: 'string', description: 'Ticket priority (LOW, MEDIUM, HIGH)' }, + hs_ticket_category: { type: 'string', description: 'Ticket category' }, + hubspot_owner_id: { type: 'string', description: 'HubSpot owner ID' }, + hs_object_id: { type: 'string', description: 'HubSpot object ID (same as record ID)' }, + createdate: { type: 'string', description: 'Ticket creation date (ISO 8601)' }, + hs_lastmodifieddate: { type: 'string', description: 'Last modified date (ISO 8601)' }, +} as const satisfies Record + +/** + * Common line item properties returned by HubSpot API. + * @see https://developers.hubspot.com/docs/api/crm/line-items + */ +export const LINE_ITEM_PROPERTIES_OUTPUT = { + name: { type: 'string', description: 'Line item name' }, + description: { type: 'string', description: 'Full description of the product' }, + hs_sku: { type: 'string', description: 'Unique product identifier (SKU)' }, + quantity: { type: 'string', description: 'Number of units included' }, + price: { type: 'string', description: 'Unit price' }, + amount: { type: 'string', description: 'Total cost (quantity * unit price)' }, + hs_line_item_currency_code: { type: 'string', description: 'Currency code' }, + recurringbillingfrequency: { type: 'string', description: 'Recurring billing frequency' }, + hs_recurring_billing_start_date: { + type: 'string', + description: 'Recurring billing start date', + }, + hs_recurring_billing_end_date: { type: 'string', description: 'Recurring billing end date' }, + hs_object_id: { type: 'string', description: 'HubSpot object ID (same as record ID)' }, + createdate: { type: 'string', description: 'Creation date (ISO 8601)' }, + hs_lastmodifieddate: { type: 'string', description: 'Last modified date (ISO 8601)' }, +} as const satisfies Record + +/** + * Common quote properties returned by HubSpot API. + * @see https://developers.hubspot.com/docs/api/crm/quotes + */ +export const QUOTE_PROPERTIES_OUTPUT = { + hs_title: { type: 'string', description: 'Quote name/title' }, + hs_expiration_date: { type: 'string', description: 'Expiration date' }, + hs_status: { type: 'string', description: 'Quote status' }, + hs_esign_enabled: { type: 'string', description: 'Whether e-signatures are enabled' }, + hs_object_id: { type: 'string', description: 'HubSpot object ID (same as record ID)' }, + createdate: { type: 'string', description: 'Creation date (ISO 8601)' }, + hs_lastmodifieddate: { type: 'string', description: 'Last modified date (ISO 8601)' }, +} as const satisfies Record + +/** + * Common appointment properties returned by HubSpot API. + * @see https://developers.hubspot.com/docs/api/crm/appointments + */ +export const APPOINTMENT_PROPERTIES_OUTPUT = { + hs_appointment_type: { type: 'string', description: 'Appointment type' }, + hs_meeting_title: { type: 'string', description: 'Meeting title' }, + hs_meeting_start_time: { type: 'string', description: 'Start time (ISO 8601)' }, + hs_meeting_end_time: { type: 'string', description: 'End time (ISO 8601)' }, + hs_meeting_location: { type: 'string', description: 'Meeting location' }, + hubspot_owner_id: { type: 'string', description: 'HubSpot owner ID' }, + hs_object_id: { type: 'string', description: 'HubSpot object ID (same as record ID)' }, + hs_createdate: { type: 'string', description: 'Creation date (ISO 8601)' }, + hs_lastmodifieddate: { type: 'string', description: 'Last modified date (ISO 8601)' }, +} as const satisfies Record + +/** + * Owner properties returned by HubSpot Owners API v3. + * @see https://developers.hubspot.com/docs/api/crm/owners + */ +export const OWNER_OUTPUT_PROPERTIES = { + id: { type: 'string', description: 'Owner ID' }, + email: { type: 'string', description: 'Owner email address' }, + firstName: { type: 'string', description: 'Owner first name' }, + lastName: { type: 'string', description: 'Owner last name' }, + userId: { type: 'number', description: 'Associated user ID', optional: true }, + teams: { + type: 'array', + description: 'Teams the owner belongs to', + optional: true, + items: { + type: 'object', + properties: { + id: { type: 'string', description: 'Team ID' }, + name: { type: 'string', description: 'Team name' }, + }, + }, + }, + createdAt: { type: 'string', description: 'Creation date (ISO 8601)' }, + updatedAt: { type: 'string', description: 'Last updated date (ISO 8601)' }, + archived: { type: 'boolean', description: 'Whether the owner is archived' }, +} as const satisfies Record + +/** + * Marketing event properties returned by HubSpot Marketing Events API. + * Response is flat (not CRM envelope) — fields are at the top level. + * @see https://developers.hubspot.com/docs/api/marketing/marketing-events + */ +export const MARKETING_EVENT_OUTPUT_PROPERTIES = { + objectId: { type: 'string', description: 'Unique event ID (HubSpot internal)' }, + eventName: { type: 'string', description: 'Event name' }, + eventType: { type: 'string', description: 'Event type', optional: true }, + eventStatus: { type: 'string', description: 'Event status', optional: true }, + eventDescription: { type: 'string', description: 'Event description', optional: true }, + eventUrl: { type: 'string', description: 'Event URL', optional: true }, + eventOrganizer: { type: 'string', description: 'Event organizer', optional: true }, + startDateTime: { type: 'string', description: 'Start date/time (ISO 8601)', optional: true }, + endDateTime: { type: 'string', description: 'End date/time (ISO 8601)', optional: true }, + eventCancelled: { type: 'boolean', description: 'Whether event is cancelled', optional: true }, + eventCompleted: { type: 'boolean', description: 'Whether event is completed', optional: true }, + registrants: { type: 'number', description: 'Number of registrants', optional: true }, + attendees: { type: 'number', description: 'Number of attendees', optional: true }, + cancellations: { type: 'number', description: 'Number of cancellations', optional: true }, + noShows: { type: 'number', description: 'Number of no-shows', optional: true }, + externalEventId: { type: 'string', description: 'External event ID', optional: true }, + createdAt: { type: 'string', description: 'Creation date (ISO 8601)' }, + updatedAt: { type: 'string', description: 'Last updated date (ISO 8601)' }, +} as const satisfies Record + +/** + * Single marketing event output definition. + */ +export const MARKETING_EVENT_OUTPUT: OutputProperty = { + type: 'object', + description: 'HubSpot marketing event', + properties: MARKETING_EVENT_OUTPUT_PROPERTIES, +} + +/** + * List properties returned by HubSpot Lists API v3. + * @see https://developers.hubspot.com/docs/api/crm/lists + */ +export const LIST_OUTPUT_PROPERTIES = { + listId: { type: 'string', description: 'List ID' }, + name: { type: 'string', description: 'List name' }, + objectTypeId: { type: 'string', description: 'Object type ID (e.g., 0-1 for contacts)' }, + processingType: { type: 'string', description: 'Processing type (MANUAL, DYNAMIC, SNAPSHOT)' }, + processingStatus: { + type: 'string', + description: 'Processing status (COMPLETE, PROCESSING)', + optional: true, + }, + listVersion: { type: 'number', description: 'List version number', optional: true }, + createdAt: { type: 'string', description: 'Creation date (ISO 8601)', optional: true }, + updatedAt: { type: 'string', description: 'Last updated date (ISO 8601)', optional: true }, +} as const satisfies Record + +/** + * Ticket object output definition with nested properties. + */ +export const TICKET_OBJECT_OUTPUT: OutputProperty = { + type: 'object', + description: 'HubSpot ticket record', + properties: { + ...CRM_RECORD_BASE_OUTPUT_PROPERTIES, + properties: { + type: 'object', + description: 'Ticket properties', + properties: TICKET_PROPERTIES_OUTPUT, + }, + associations: { + type: 'object', + description: 'Associated records (contacts, companies, etc.)', + optional: true, + }, + }, +} + +/** + * Line item object output definition with nested properties. + */ +export const LINE_ITEM_OBJECT_OUTPUT: OutputProperty = { + type: 'object', + description: 'HubSpot line item record', + properties: { + ...CRM_RECORD_BASE_OUTPUT_PROPERTIES, + properties: { + type: 'object', + description: 'Line item properties', + properties: LINE_ITEM_PROPERTIES_OUTPUT, + }, + associations: { + type: 'object', + description: 'Associated records (deals, quotes, etc.)', + optional: true, + }, + }, +} + +/** + * Quote object output definition with nested properties. + */ +export const QUOTE_OBJECT_OUTPUT: OutputProperty = { + type: 'object', + description: 'HubSpot quote record', + properties: { + ...CRM_RECORD_BASE_OUTPUT_PROPERTIES, + properties: { + type: 'object', + description: 'Quote properties', + properties: QUOTE_PROPERTIES_OUTPUT, + }, + associations: { + type: 'object', + description: 'Associated records (deals, line items, etc.)', + optional: true, + }, + }, +} + +/** + * Appointment object output definition with nested properties. + */ +export const APPOINTMENT_OBJECT_OUTPUT: OutputProperty = { + type: 'object', + description: 'HubSpot appointment record', + properties: { + ...CRM_RECORD_BASE_OUTPUT_PROPERTIES, + properties: { + type: 'object', + description: 'Appointment properties', + properties: APPOINTMENT_PROPERTIES_OUTPUT, + }, + associations: { + type: 'object', + description: 'Associated records (contacts, companies, etc.)', + optional: true, + }, + }, +} + +/** + * Generic CRM object output for objects with dynamic properties (carts). + */ +export const GENERIC_CRM_OBJECT_OUTPUT: OutputProperty = { + type: 'object', + description: 'HubSpot CRM record', + properties: { + ...CRM_RECORD_BASE_OUTPUT_PROPERTIES, + properties: { + type: 'object', + description: 'Record properties', + }, + associations: { + type: 'object', + description: 'Associated records', + optional: true, + }, + }, +} + +/** + * Tickets array output definition for list endpoints. + */ +export const TICKETS_ARRAY_OUTPUT: OutputProperty = { + type: 'array', + description: 'Array of HubSpot ticket records', + items: { + type: 'object', + properties: { + ...CRM_RECORD_BASE_OUTPUT_PROPERTIES, + properties: { + type: 'object', + description: 'Ticket properties', + properties: TICKET_PROPERTIES_OUTPUT, + }, + associations: { type: 'object', description: 'Associated records', optional: true }, + }, + }, +} + +/** + * Line items array output definition for list endpoints. + */ +export const LINE_ITEMS_ARRAY_OUTPUT: OutputProperty = { + type: 'array', + description: 'Array of HubSpot line item records', + items: { + type: 'object', + properties: { + ...CRM_RECORD_BASE_OUTPUT_PROPERTIES, + properties: { + type: 'object', + description: 'Line item properties', + properties: LINE_ITEM_PROPERTIES_OUTPUT, + }, + associations: { type: 'object', description: 'Associated records', optional: true }, + }, + }, +} + +/** + * Quotes array output definition for list endpoints. + */ +export const QUOTES_ARRAY_OUTPUT: OutputProperty = { + type: 'array', + description: 'Array of HubSpot quote records', + items: { + type: 'object', + properties: { + ...CRM_RECORD_BASE_OUTPUT_PROPERTIES, + properties: { + type: 'object', + description: 'Quote properties', + properties: QUOTE_PROPERTIES_OUTPUT, + }, + associations: { type: 'object', description: 'Associated records', optional: true }, + }, + }, +} + +/** + * Appointments array output definition for list endpoints. + */ +export const APPOINTMENTS_ARRAY_OUTPUT: OutputProperty = { + type: 'array', + description: 'Array of HubSpot appointment records', + items: { + type: 'object', + properties: { + ...CRM_RECORD_BASE_OUTPUT_PROPERTIES, + properties: { + type: 'object', + description: 'Appointment properties', + properties: APPOINTMENT_PROPERTIES_OUTPUT, + }, + associations: { type: 'object', description: 'Associated records', optional: true }, + }, + }, +} + +/** + * Generic CRM objects array output for objects with dynamic properties (carts). + */ +export const GENERIC_CRM_ARRAY_OUTPUT: OutputProperty = { + type: 'array', + description: 'Array of HubSpot CRM records', + items: { + type: 'object', + properties: { + ...CRM_RECORD_BASE_OUTPUT_PROPERTIES, + properties: { type: 'object', description: 'Record properties' }, + associations: { type: 'object', description: 'Associated records', optional: true }, + }, + }, +} + +/** + * Owners array output definition for list endpoints. + */ +export const OWNERS_ARRAY_OUTPUT: OutputProperty = { + type: 'array', + description: 'Array of HubSpot owner objects', + items: { + type: 'object', + properties: OWNER_OUTPUT_PROPERTIES, + }, +} + +/** + * Marketing events array output definition for list endpoints. + */ +export const MARKETING_EVENTS_ARRAY_OUTPUT: OutputProperty = { + type: 'array', + description: 'Array of HubSpot marketing event objects', + items: { + type: 'object', + properties: MARKETING_EVENT_OUTPUT_PROPERTIES, + }, +} + +/** + * Lists array output definition for list endpoints. + */ +export const LISTS_ARRAY_OUTPUT: OutputProperty = { + type: 'array', + description: 'Array of HubSpot list objects', + items: { + type: 'object', + properties: LIST_OUTPUT_PROPERTIES, + }, +} + /** * User properties returned by HubSpot Settings API v3. * Note: firstName and lastName are NOT returned by the Settings API. @@ -334,7 +722,7 @@ export interface HubSpotUser { superAdmin?: boolean } -export interface HubSpotContact { +export interface HubSpotCrmObject { id: string properties: Record createdAt: string @@ -343,6 +731,9 @@ export interface HubSpotContact { associations?: Record } +/** @deprecated Use HubSpotCrmObject instead */ +export type HubSpotContact = HubSpotCrmObject + export interface HubSpotPaging { next?: { after: string @@ -354,6 +745,7 @@ export interface HubSpotPaging { export interface HubSpotGetUsersResponse extends ToolResponse { output: { users: HubSpotUser[] + paging: HubSpotPaging | null totalItems?: number success: boolean } @@ -362,6 +754,7 @@ export interface HubSpotGetUsersResponse extends ToolResponse { export interface HubSpotGetUsersParams { accessToken: string limit?: string + after?: string } // List Contacts @@ -473,7 +866,7 @@ export interface HubSpotSearchContactsParams { } // Companies (same structure as contacts) -export type HubSpotCompany = HubSpotContact +export type HubSpotCompany = HubSpotCrmObject export type HubSpotListCompaniesParams = HubSpotListContactsParams export type HubSpotListCompaniesResponse = Omit & { output: { @@ -524,17 +917,301 @@ export interface HubSpotSearchCompaniesResponse extends ToolResponse { } } -// Deals (same structure as contacts) -export type HubSpotDeal = HubSpotContact +// Deals +export type HubSpotDeal = HubSpotCrmObject export type HubSpotListDealsParams = HubSpotListContactsParams export type HubSpotListDealsResponse = Omit & { output: { deals: HubSpotContact[] paging?: HubSpotPaging - metadata: { - totalReturned: number - hasMore: boolean - } + metadata: { totalReturned: number; hasMore: boolean } + success: boolean + } +} +export type HubSpotGetDealParams = Omit & { dealId: string } +export type HubSpotGetDealResponse = ToolResponse & { + output: { deal: HubSpotContact; dealId: string; success: boolean } +} +export type HubSpotCreateDealParams = HubSpotCreateContactParams +export type HubSpotCreateDealResponse = ToolResponse & { + output: { deal: HubSpotContact; dealId: string; success: boolean } +} +export type HubSpotUpdateDealParams = Omit & { + dealId: string +} +export type HubSpotUpdateDealResponse = ToolResponse & { + output: { deal: HubSpotContact; dealId: string; success: boolean } +} +export type HubSpotSearchDealsParams = HubSpotSearchContactsParams +export interface HubSpotSearchDealsResponse extends ToolResponse { + output: { + deals: HubSpotContact[] + total: number + paging?: HubSpotPaging + metadata: { totalReturned: number; hasMore: boolean } + success: boolean + } +} + +// Tickets +export type HubSpotTicket = HubSpotCrmObject +export type HubSpotListTicketsParams = HubSpotListContactsParams +export type HubSpotListTicketsResponse = ToolResponse & { + output: { + tickets: HubSpotContact[] + paging?: HubSpotPaging + metadata: { totalReturned: number; hasMore: boolean } + success: boolean + } +} +export type HubSpotGetTicketParams = Omit & { + ticketId: string +} +export type HubSpotGetTicketResponse = ToolResponse & { + output: { ticket: HubSpotContact; ticketId: string; success: boolean } +} +export type HubSpotCreateTicketParams = HubSpotCreateContactParams +export type HubSpotCreateTicketResponse = ToolResponse & { + output: { ticket: HubSpotContact; ticketId: string; success: boolean } +} +export type HubSpotUpdateTicketParams = Omit & { + ticketId: string +} +export type HubSpotUpdateTicketResponse = ToolResponse & { + output: { ticket: HubSpotContact; ticketId: string; success: boolean } +} +export type HubSpotSearchTicketsParams = HubSpotSearchContactsParams +export interface HubSpotSearchTicketsResponse extends ToolResponse { + output: { + tickets: HubSpotContact[] + total: number + paging?: HubSpotPaging + metadata: { totalReturned: number; hasMore: boolean } + success: boolean + } +} + +// Line Items +export type HubSpotLineItem = HubSpotCrmObject +export type HubSpotListLineItemsParams = HubSpotListContactsParams +export type HubSpotListLineItemsResponse = ToolResponse & { + output: { + lineItems: HubSpotContact[] + paging?: HubSpotPaging + metadata: { totalReturned: number; hasMore: boolean } + success: boolean + } +} +export type HubSpotGetLineItemParams = Omit & { + lineItemId: string +} +export type HubSpotGetLineItemResponse = ToolResponse & { + output: { lineItem: HubSpotContact; lineItemId: string; success: boolean } +} +export type HubSpotCreateLineItemParams = HubSpotCreateContactParams +export type HubSpotCreateLineItemResponse = ToolResponse & { + output: { lineItem: HubSpotContact; lineItemId: string; success: boolean } +} +export type HubSpotUpdateLineItemParams = Omit & { + lineItemId: string +} +export type HubSpotUpdateLineItemResponse = ToolResponse & { + output: { lineItem: HubSpotContact; lineItemId: string; success: boolean } +} + +// Quotes +export type HubSpotQuote = HubSpotCrmObject +export type HubSpotListQuotesParams = HubSpotListContactsParams +export type HubSpotListQuotesResponse = ToolResponse & { + output: { + quotes: HubSpotContact[] + paging?: HubSpotPaging + metadata: { totalReturned: number; hasMore: boolean } + success: boolean + } +} +export type HubSpotGetQuoteParams = Omit & { + quoteId: string +} +export type HubSpotGetQuoteResponse = ToolResponse & { + output: { quote: HubSpotContact; quoteId: string; success: boolean } +} + +// Appointments +export type HubSpotAppointment = HubSpotCrmObject +export type HubSpotListAppointmentsParams = HubSpotListContactsParams +export type HubSpotListAppointmentsResponse = ToolResponse & { + output: { + appointments: HubSpotContact[] + paging?: HubSpotPaging + metadata: { totalReturned: number; hasMore: boolean } + success: boolean + } +} +export type HubSpotGetAppointmentParams = Omit & { + appointmentId: string +} +export type HubSpotGetAppointmentResponse = ToolResponse & { + output: { appointment: HubSpotContact; appointmentId: string; success: boolean } +} +export type HubSpotCreateAppointmentParams = HubSpotCreateContactParams +export type HubSpotCreateAppointmentResponse = ToolResponse & { + output: { appointment: HubSpotContact; appointmentId: string; success: boolean } +} +export type HubSpotUpdateAppointmentParams = Omit & { + appointmentId: string +} +export type HubSpotUpdateAppointmentResponse = ToolResponse & { + output: { appointment: HubSpotContact; appointmentId: string; success: boolean } +} + +// Carts +export type HubSpotCart = HubSpotCrmObject +export type HubSpotListCartsParams = HubSpotListContactsParams +export type HubSpotListCartsResponse = ToolResponse & { + output: { + carts: HubSpotContact[] + paging?: HubSpotPaging + metadata: { totalReturned: number; hasMore: boolean } + success: boolean + } +} +export type HubSpotGetCartParams = Omit & { + cartId: string +} +export type HubSpotGetCartResponse = ToolResponse & { + output: { cart: HubSpotContact; cartId: string; success: boolean } +} + +// Owners +export interface HubSpotOwner { + id: string + email: string + firstName: string + lastName: string + userId?: number + teams?: Array<{ id: string; name: string }> + createdAt: string + updatedAt: string + archived: boolean +} + +export interface HubSpotListOwnersParams { + accessToken: string + limit?: string + after?: string + email?: string +} + +export interface HubSpotListOwnersResponse extends ToolResponse { + output: { + owners: HubSpotOwner[] + paging?: HubSpotPaging + metadata: { totalReturned: number; hasMore: boolean } + success: boolean + } +} + +// Marketing Events +export interface HubSpotMarketingEvent { + objectId: string + eventName: string + eventType?: string + eventStatus?: string + eventDescription?: string + eventUrl?: string + eventOrganizer?: string + startDateTime?: string + endDateTime?: string + eventCancelled: boolean + eventCompleted: boolean + registrants: number + attendees: number + cancellations: number + noShows: number + createdAt: string + updatedAt: string +} + +export interface HubSpotListMarketingEventsParams { + accessToken: string + limit?: string + after?: string +} + +export interface HubSpotListMarketingEventsResponse extends ToolResponse { + output: { + events: HubSpotMarketingEvent[] + paging?: HubSpotPaging + metadata: { totalReturned: number; hasMore: boolean } + success: boolean + } +} + +export interface HubSpotGetMarketingEventParams { + accessToken: string + eventId: string +} + +export interface HubSpotGetMarketingEventResponse extends ToolResponse { + output: { + event: HubSpotMarketingEvent + eventId: string + success: boolean + } +} + +// Lists +export interface HubSpotList { + listId: string + name: string + objectTypeId: string + processingType: string + size?: number + createdAt?: string + updatedAt?: string +} + +export interface HubSpotListListsParams { + accessToken: string + query?: string + count?: string + offset?: string +} + +export interface HubSpotListListsResponse extends ToolResponse { + output: { + lists: HubSpotList[] + paging?: HubSpotPaging + metadata: { totalReturned: number; total: number | null; hasMore: boolean } + success: boolean + } +} + +export interface HubSpotGetListParams { + accessToken: string + listId: string +} + +export interface HubSpotGetListResponse extends ToolResponse { + output: { + list: HubSpotList + listId: string + success: boolean + } +} + +export interface HubSpotCreateListParams { + accessToken: string + name: string + objectTypeId: string + processingType: string +} + +export interface HubSpotCreateListResponse extends ToolResponse { + output: { + list: HubSpotList + listId: string success: boolean } } @@ -553,3 +1230,30 @@ export type HubSpotResponse = | HubSpotUpdateCompanyResponse | HubSpotSearchCompaniesResponse | HubSpotListDealsResponse + | HubSpotGetDealResponse + | HubSpotCreateDealResponse + | HubSpotUpdateDealResponse + | HubSpotSearchDealsResponse + | HubSpotListTicketsResponse + | HubSpotGetTicketResponse + | HubSpotCreateTicketResponse + | HubSpotUpdateTicketResponse + | HubSpotSearchTicketsResponse + | HubSpotListLineItemsResponse + | HubSpotGetLineItemResponse + | HubSpotCreateLineItemResponse + | HubSpotUpdateLineItemResponse + | HubSpotListQuotesResponse + | HubSpotGetQuoteResponse + | HubSpotListAppointmentsResponse + | HubSpotGetAppointmentResponse + | HubSpotCreateAppointmentResponse + | HubSpotUpdateAppointmentResponse + | HubSpotListCartsResponse + | HubSpotGetCartResponse + | HubSpotListOwnersResponse + | HubSpotListMarketingEventsResponse + | HubSpotGetMarketingEventResponse + | HubSpotListListsResponse + | HubSpotGetListResponse + | HubSpotCreateListResponse diff --git a/apps/sim/tools/hubspot/update_appointment.ts b/apps/sim/tools/hubspot/update_appointment.ts new file mode 100644 index 00000000000..54854d1aadc --- /dev/null +++ b/apps/sim/tools/hubspot/update_appointment.ts @@ -0,0 +1,101 @@ +import { createLogger } from '@sim/logger' +import type { + HubSpotUpdateAppointmentParams, + HubSpotUpdateAppointmentResponse, +} from '@/tools/hubspot/types' +import { APPOINTMENT_OBJECT_OUTPUT } from '@/tools/hubspot/types' +import type { ToolConfig } from '@/tools/types' + +const logger = createLogger('HubSpotUpdateAppointment') + +export const hubspotUpdateAppointmentTool: ToolConfig< + HubSpotUpdateAppointmentParams, + HubSpotUpdateAppointmentResponse +> = { + id: 'hubspot_update_appointment', + name: 'Update Appointment in HubSpot', + description: 'Update an existing appointment in HubSpot by ID', + version: '1.0.0', + + oauth: { + required: true, + provider: 'hubspot', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'The access token for the HubSpot API', + }, + appointmentId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The HubSpot appointment ID to update', + }, + idProperty: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Property to use as unique identifier. If not specified, uses record ID', + }, + properties: { + type: 'object', + required: true, + visibility: 'user-or-llm', + description: + 'Appointment properties to update as JSON object (e.g., {"hs_meeting_title": "Updated Call", "hs_meeting_location": "Zoom"})', + }, + }, + + request: { + url: (params) => { + const baseUrl = `https://api.hubapi.com/crm/v3/objects/appointments/${params.appointmentId.trim()}` + const queryParams = new URLSearchParams() + if (params.idProperty) queryParams.append('idProperty', params.idProperty) + const queryString = queryParams.toString() + return queryString ? `${baseUrl}?${queryString}` : baseUrl + }, + method: 'PATCH', + headers: (params) => { + if (!params.accessToken) { + throw new Error('Access token is required') + } + return { + Authorization: `Bearer ${params.accessToken}`, + 'Content-Type': 'application/json', + } + }, + body: (params) => { + let properties = params.properties + if (typeof properties === 'string') { + try { + properties = JSON.parse(properties) + } catch (e) { + throw new Error('Invalid JSON format for properties. Please provide a valid JSON object.') + } + } + return { properties } + }, + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + if (!response.ok) { + logger.error('HubSpot API request failed', { data, status: response.status }) + throw new Error(data.message || 'Failed to update appointment in HubSpot') + } + return { + success: true, + output: { appointment: data, appointmentId: data.id, success: true }, + } + }, + + outputs: { + appointment: APPOINTMENT_OBJECT_OUTPUT, + appointmentId: { type: 'string', description: 'The updated appointment ID' }, + success: { type: 'boolean', description: 'Operation success status' }, + }, +} diff --git a/apps/sim/tools/hubspot/update_company.ts b/apps/sim/tools/hubspot/update_company.ts index 866a9717bd4..949b1ad3b65 100644 --- a/apps/sim/tools/hubspot/update_company.ts +++ b/apps/sim/tools/hubspot/update_company.ts @@ -53,7 +53,7 @@ export const hubspotUpdateCompanyTool: ToolConfig< request: { url: (params) => { - const baseUrl = `https://api.hubapi.com/crm/v3/objects/companies/${params.companyId}` + const baseUrl = `https://api.hubapi.com/crm/v3/objects/companies/${params.companyId.trim()}` if (params.idProperty) { return `${baseUrl}?idProperty=${params.idProperty}` } diff --git a/apps/sim/tools/hubspot/update_contact.ts b/apps/sim/tools/hubspot/update_contact.ts index a5f3c4d68f8..3324d97a955 100644 --- a/apps/sim/tools/hubspot/update_contact.ts +++ b/apps/sim/tools/hubspot/update_contact.ts @@ -53,7 +53,7 @@ export const hubspotUpdateContactTool: ToolConfig< request: { url: (params) => { - const baseUrl = `https://api.hubapi.com/crm/v3/objects/contacts/${params.contactId}` + const baseUrl = `https://api.hubapi.com/crm/v3/objects/contacts/${params.contactId.trim()}` if (params.idProperty) { return `${baseUrl}?idProperty=${params.idProperty}` } diff --git a/apps/sim/tools/hubspot/update_deal.ts b/apps/sim/tools/hubspot/update_deal.ts new file mode 100644 index 00000000000..a9a9f3571b6 --- /dev/null +++ b/apps/sim/tools/hubspot/update_deal.ts @@ -0,0 +1,98 @@ +import { createLogger } from '@sim/logger' +import type { HubSpotUpdateDealParams, HubSpotUpdateDealResponse } from '@/tools/hubspot/types' +import { DEAL_OBJECT_OUTPUT } from '@/tools/hubspot/types' +import type { ToolConfig } from '@/tools/types' + +const logger = createLogger('HubSpotUpdateDeal') + +export const hubspotUpdateDealTool: ToolConfig = + { + id: 'hubspot_update_deal', + name: 'Update Deal in HubSpot', + description: 'Update an existing deal in HubSpot by ID', + version: '1.0.0', + + oauth: { + required: true, + provider: 'hubspot', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'The access token for the HubSpot API', + }, + dealId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The HubSpot deal ID to update', + }, + idProperty: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Property to use as unique identifier. If not specified, uses record ID', + }, + properties: { + type: 'object', + required: true, + visibility: 'user-or-llm', + description: + 'Deal properties to update as JSON object (e.g., {"amount": "10000", "dealstage": "closedwon"})', + }, + }, + + request: { + url: (params) => { + const baseUrl = `https://api.hubapi.com/crm/v3/objects/deals/${params.dealId.trim()}` + const queryParams = new URLSearchParams() + if (params.idProperty) queryParams.append('idProperty', params.idProperty) + const queryString = queryParams.toString() + return queryString ? `${baseUrl}?${queryString}` : baseUrl + }, + method: 'PATCH', + headers: (params) => { + if (!params.accessToken) { + throw new Error('Access token is required') + } + return { + Authorization: `Bearer ${params.accessToken}`, + 'Content-Type': 'application/json', + } + }, + body: (params) => { + let properties = params.properties + if (typeof properties === 'string') { + try { + properties = JSON.parse(properties) + } catch (e) { + throw new Error( + 'Invalid JSON format for properties. Please provide a valid JSON object.' + ) + } + } + return { properties } + }, + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + if (!response.ok) { + logger.error('HubSpot API request failed', { data, status: response.status }) + throw new Error(data.message || 'Failed to update deal in HubSpot') + } + return { + success: true, + output: { deal: data, dealId: data.id, success: true }, + } + }, + + outputs: { + deal: DEAL_OBJECT_OUTPUT, + dealId: { type: 'string', description: 'The updated deal ID' }, + success: { type: 'boolean', description: 'Operation success status' }, + }, + } diff --git a/apps/sim/tools/hubspot/update_line_item.ts b/apps/sim/tools/hubspot/update_line_item.ts new file mode 100644 index 00000000000..2bfe86615bc --- /dev/null +++ b/apps/sim/tools/hubspot/update_line_item.ts @@ -0,0 +1,101 @@ +import { createLogger } from '@sim/logger' +import type { + HubSpotUpdateLineItemParams, + HubSpotUpdateLineItemResponse, +} from '@/tools/hubspot/types' +import { LINE_ITEM_OBJECT_OUTPUT } from '@/tools/hubspot/types' +import type { ToolConfig } from '@/tools/types' + +const logger = createLogger('HubSpotUpdateLineItem') + +export const hubspotUpdateLineItemTool: ToolConfig< + HubSpotUpdateLineItemParams, + HubSpotUpdateLineItemResponse +> = { + id: 'hubspot_update_line_item', + name: 'Update Line Item in HubSpot', + description: 'Update an existing line item in HubSpot by ID', + version: '1.0.0', + + oauth: { + required: true, + provider: 'hubspot', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'The access token for the HubSpot API', + }, + lineItemId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The HubSpot line item ID to update', + }, + idProperty: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Property to use as unique identifier. If not specified, uses record ID', + }, + properties: { + type: 'object', + required: true, + visibility: 'user-or-llm', + description: + 'Line item properties to update as JSON object (e.g., {"quantity": "5", "price": "25.00"})', + }, + }, + + request: { + url: (params) => { + const baseUrl = `https://api.hubapi.com/crm/v3/objects/line_items/${params.lineItemId.trim()}` + const queryParams = new URLSearchParams() + if (params.idProperty) queryParams.append('idProperty', params.idProperty) + const queryString = queryParams.toString() + return queryString ? `${baseUrl}?${queryString}` : baseUrl + }, + method: 'PATCH', + headers: (params) => { + if (!params.accessToken) { + throw new Error('Access token is required') + } + return { + Authorization: `Bearer ${params.accessToken}`, + 'Content-Type': 'application/json', + } + }, + body: (params) => { + let properties = params.properties + if (typeof properties === 'string') { + try { + properties = JSON.parse(properties) + } catch (e) { + throw new Error('Invalid JSON format for properties. Please provide a valid JSON object.') + } + } + return { properties } + }, + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + if (!response.ok) { + logger.error('HubSpot API request failed', { data, status: response.status }) + throw new Error(data.message || 'Failed to update line item in HubSpot') + } + return { + success: true, + output: { lineItem: data, lineItemId: data.id, success: true }, + } + }, + + outputs: { + lineItem: LINE_ITEM_OBJECT_OUTPUT, + lineItemId: { type: 'string', description: 'The updated line item ID' }, + success: { type: 'boolean', description: 'Operation success status' }, + }, +} diff --git a/apps/sim/tools/hubspot/update_ticket.ts b/apps/sim/tools/hubspot/update_ticket.ts new file mode 100644 index 00000000000..2741de503df --- /dev/null +++ b/apps/sim/tools/hubspot/update_ticket.ts @@ -0,0 +1,98 @@ +import { createLogger } from '@sim/logger' +import type { HubSpotUpdateTicketParams, HubSpotUpdateTicketResponse } from '@/tools/hubspot/types' +import { TICKET_OBJECT_OUTPUT } from '@/tools/hubspot/types' +import type { ToolConfig } from '@/tools/types' + +const logger = createLogger('HubSpotUpdateTicket') + +export const hubspotUpdateTicketTool: ToolConfig< + HubSpotUpdateTicketParams, + HubSpotUpdateTicketResponse +> = { + id: 'hubspot_update_ticket', + name: 'Update Ticket in HubSpot', + description: 'Update an existing ticket in HubSpot by ID', + version: '1.0.0', + + oauth: { + required: true, + provider: 'hubspot', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'The access token for the HubSpot API', + }, + ticketId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The HubSpot ticket ID to update', + }, + idProperty: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Property to use as unique identifier. If not specified, uses record ID', + }, + properties: { + type: 'object', + required: true, + visibility: 'user-or-llm', + description: + 'Ticket properties to update as JSON object (e.g., {"subject": "Updated subject", "hs_ticket_priority": "HIGH"})', + }, + }, + + request: { + url: (params) => { + const baseUrl = `https://api.hubapi.com/crm/v3/objects/tickets/${params.ticketId.trim()}` + const queryParams = new URLSearchParams() + if (params.idProperty) queryParams.append('idProperty', params.idProperty) + const queryString = queryParams.toString() + return queryString ? `${baseUrl}?${queryString}` : baseUrl + }, + method: 'PATCH', + headers: (params) => { + if (!params.accessToken) { + throw new Error('Access token is required') + } + return { + Authorization: `Bearer ${params.accessToken}`, + 'Content-Type': 'application/json', + } + }, + body: (params) => { + let properties = params.properties + if (typeof properties === 'string') { + try { + properties = JSON.parse(properties) + } catch (e) { + throw new Error('Invalid JSON format for properties. Please provide a valid JSON object.') + } + } + return { properties } + }, + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + if (!response.ok) { + logger.error('HubSpot API request failed', { data, status: response.status }) + throw new Error(data.message || 'Failed to update ticket in HubSpot') + } + return { + success: true, + output: { ticket: data, ticketId: data.id, success: true }, + } + }, + + outputs: { + ticket: TICKET_OBJECT_OUTPUT, + ticketId: { type: 'string', description: 'The updated ticket ID' }, + success: { type: 'boolean', description: 'Operation success status' }, + }, +} diff --git a/apps/sim/tools/registry.ts b/apps/sim/tools/registry.ts index be98b26b3de..34c48ea4578 100644 --- a/apps/sim/tools/registry.ts +++ b/apps/sim/tools/registry.ts @@ -1020,18 +1020,45 @@ import { } from '@/tools/hex' import { httpRequestTool, webhookRequestTool } from '@/tools/http' import { + hubspotCreateAppointmentTool, hubspotCreateCompanyTool, hubspotCreateContactTool, + hubspotCreateDealTool, + hubspotCreateLineItemTool, + hubspotCreateListTool, + hubspotCreateTicketTool, + hubspotGetAppointmentTool, + hubspotGetCartTool, hubspotGetCompanyTool, hubspotGetContactTool, + hubspotGetDealTool, + hubspotGetLineItemTool, + hubspotGetListTool, + hubspotGetMarketingEventTool, + hubspotGetQuoteTool, + hubspotGetTicketTool, hubspotGetUsersTool, + hubspotListAppointmentsTool, + hubspotListCartsTool, hubspotListCompaniesTool, hubspotListContactsTool, hubspotListDealsTool, + hubspotListLineItemsTool, + hubspotListListsTool, + hubspotListMarketingEventsTool, + hubspotListOwnersTool, + hubspotListQuotesTool, + hubspotListTicketsTool, hubspotSearchCompaniesTool, hubspotSearchContactsTool, + hubspotSearchDealsTool, + hubspotSearchTicketsTool, + hubspotUpdateAppointmentTool, hubspotUpdateCompanyTool, hubspotUpdateContactTool, + hubspotUpdateDealTool, + hubspotUpdateLineItemTool, + hubspotUpdateTicketTool, } from '@/tools/hubspot' import { huggingfaceChatTool } from '@/tools/huggingface' import { @@ -4338,18 +4365,45 @@ export const tools: Record = { infisical_create_secret: infisicalCreateSecretTool, infisical_update_secret: infisicalUpdateSecretTool, infisical_delete_secret: infisicalDeleteSecretTool, + hubspot_create_appointment: hubspotCreateAppointmentTool, hubspot_create_company: hubspotCreateCompanyTool, hubspot_create_contact: hubspotCreateContactTool, + hubspot_create_deal: hubspotCreateDealTool, + hubspot_create_line_item: hubspotCreateLineItemTool, + hubspot_create_list: hubspotCreateListTool, + hubspot_create_ticket: hubspotCreateTicketTool, + hubspot_get_appointment: hubspotGetAppointmentTool, + hubspot_get_cart: hubspotGetCartTool, hubspot_get_company: hubspotGetCompanyTool, hubspot_get_contact: hubspotGetContactTool, + hubspot_get_deal: hubspotGetDealTool, + hubspot_get_line_item: hubspotGetLineItemTool, + hubspot_get_list: hubspotGetListTool, + hubspot_get_marketing_event: hubspotGetMarketingEventTool, + hubspot_get_quote: hubspotGetQuoteTool, + hubspot_get_ticket: hubspotGetTicketTool, hubspot_get_users: hubspotGetUsersTool, + hubspot_list_appointments: hubspotListAppointmentsTool, + hubspot_list_carts: hubspotListCartsTool, hubspot_list_companies: hubspotListCompaniesTool, hubspot_list_contacts: hubspotListContactsTool, hubspot_list_deals: hubspotListDealsTool, + hubspot_list_line_items: hubspotListLineItemsTool, + hubspot_list_lists: hubspotListListsTool, + hubspot_list_marketing_events: hubspotListMarketingEventsTool, + hubspot_list_owners: hubspotListOwnersTool, + hubspot_list_quotes: hubspotListQuotesTool, + hubspot_list_tickets: hubspotListTicketsTool, hubspot_search_companies: hubspotSearchCompaniesTool, hubspot_search_contacts: hubspotSearchContactsTool, + hubspot_search_deals: hubspotSearchDealsTool, + hubspot_search_tickets: hubspotSearchTicketsTool, + hubspot_update_appointment: hubspotUpdateAppointmentTool, hubspot_update_company: hubspotUpdateCompanyTool, hubspot_update_contact: hubspotUpdateContactTool, + hubspot_update_deal: hubspotUpdateDealTool, + hubspot_update_line_item: hubspotUpdateLineItemTool, + hubspot_update_ticket: hubspotUpdateTicketTool, sharepoint_create_page: sharepointCreatePageTool, sharepoint_read_page: sharepointReadPageTool, sharepoint_list_sites: sharepointListSitesTool,