Skip to content

Commit 0640c0b

Browse files
authored
improvement(hubspot): align tools with API docs and expand coverage with delete, list membership, and search tools (#5635)
1 parent b1bd2b5 commit 0640c0b

28 files changed

Lines changed: 1614 additions & 40 deletions

apps/sim/blocks/blocks/hubspot.ts

Lines changed: 172 additions & 6 deletions
Large diffs are not rendered by default.
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import { createLogger } from '@sim/logger'
2+
import type {
3+
HubSpotAddListMembershipsParams,
4+
HubSpotAddListMembershipsResponse,
5+
} from '@/tools/hubspot/types'
6+
import type { ToolConfig } from '@/tools/types'
7+
8+
const logger = createLogger('HubSpotAddListMemberships')
9+
10+
export const hubspotAddListMembershipsTool: ToolConfig<
11+
HubSpotAddListMembershipsParams,
12+
HubSpotAddListMembershipsResponse
13+
> = {
14+
id: 'hubspot_add_list_memberships',
15+
name: 'Add List Members in HubSpot',
16+
description: 'Add records to a manual (static) HubSpot list by record ID',
17+
version: '1.0.0',
18+
19+
oauth: {
20+
required: true,
21+
provider: 'hubspot',
22+
},
23+
24+
params: {
25+
accessToken: {
26+
type: 'string',
27+
required: true,
28+
visibility: 'hidden',
29+
description: 'The access token for the HubSpot API',
30+
},
31+
listId: {
32+
type: 'string',
33+
required: true,
34+
visibility: 'user-or-llm',
35+
description: 'The ID of the list to add records to (MANUAL or SNAPSHOT lists only)',
36+
},
37+
recordIds: {
38+
type: 'array',
39+
required: true,
40+
visibility: 'user-or-llm',
41+
description:
42+
'Record IDs to add to the list, as a JSON array (e.g., ["123","456"]) or comma-separated string',
43+
},
44+
},
45+
46+
request: {
47+
url: (params) => `https://api.hubapi.com/crm/v3/lists/${params.listId.trim()}/memberships/add`,
48+
method: 'PUT',
49+
headers: (params) => {
50+
if (!params.accessToken) {
51+
throw new Error('Access token is required')
52+
}
53+
return {
54+
Authorization: `Bearer ${params.accessToken}`,
55+
'Content-Type': 'application/json',
56+
}
57+
},
58+
body: (params) => {
59+
let recordIds = params.recordIds
60+
if (typeof recordIds === 'string') {
61+
const trimmed = recordIds.trim()
62+
if (trimmed.startsWith('[')) {
63+
try {
64+
recordIds = JSON.parse(trimmed)
65+
} catch (e) {
66+
throw new Error(`Invalid JSON for recordIds: ${(e as Error).message}`)
67+
}
68+
} else {
69+
recordIds = trimmed.split(',')
70+
}
71+
}
72+
if (!Array.isArray(recordIds)) {
73+
throw new Error('recordIds must be an array of record IDs')
74+
}
75+
const ids = recordIds.map((id) => String(id).trim()).filter(Boolean)
76+
if (ids.length === 0) {
77+
throw new Error('At least one record ID is required')
78+
}
79+
return ids
80+
},
81+
},
82+
83+
transformResponse: async (response: Response) => {
84+
const data = await response.json().catch(() => ({}))
85+
if (!response.ok) {
86+
logger.error('HubSpot API request failed', { data, status: response.status })
87+
throw new Error(data.message || 'Failed to add records to HubSpot list')
88+
}
89+
return {
90+
success: true,
91+
output: {
92+
recordIdsAdded: data.recordIdsAdded ?? [],
93+
recordIdsMissing: data.recordIdsMissing ?? [],
94+
success: true,
95+
},
96+
}
97+
},
98+
99+
outputs: {
100+
recordIdsAdded: {
101+
type: 'array',
102+
description: 'IDs of the records that were added to the list',
103+
items: { type: 'string' },
104+
},
105+
recordIdsMissing: {
106+
type: 'array',
107+
description: 'IDs of the requested records that were not found',
108+
items: { type: 'string' },
109+
},
110+
success: { type: 'boolean', description: 'Operation success status' },
111+
},
112+
}

apps/sim/tools/hubspot/create_appointment.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export const hubspotCreateAppointmentTool: ToolConfig<
3434
required: true,
3535
visibility: 'user-or-llm',
3636
description:
37-
'Appointment properties as JSON object (e.g., {"hs_appointment_name": "Discovery Call", "hs_appointment_start": "2024-01-15T10:00:00Z", "hs_appointment_end": "2024-01-15T11:00:00Z"})',
37+
'Appointment properties as JSON object. Must include hs_appointment_start (e.g., {"hs_appointment_name": "Discovery Call", "hs_appointment_start": "2024-01-15T10:00:00Z", "hs_appointment_end": "2024-01-15T11:00:00Z"})',
3838
},
3939
associations: {
4040
type: 'array',

apps/sim/tools/hubspot/create_deal.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export const hubspotCreateDealTool: ToolConfig<HubSpotCreateDealParams, HubSpotC
3030
required: true,
3131
visibility: 'user-or-llm',
3232
description:
33-
'Deal properties as JSON object. Must include dealname (e.g., {"dealname": "New Deal", "amount": "5000", "dealstage": "appointmentscheduled"})',
33+
'Deal properties as JSON object. Should include dealname and dealstage, plus pipeline when the account has multiple pipelines (e.g., {"dealname": "New Deal", "amount": "5000", "dealstage": "appointmentscheduled"})',
3434
},
3535
associations: {
3636
type: 'array',
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import { createLogger } from '@sim/logger'
2+
import type {
3+
HubSpotDeleteAssociationParams,
4+
HubSpotDeleteAssociationResponse,
5+
} from '@/tools/hubspot/types'
6+
import type { ToolConfig } from '@/tools/types'
7+
8+
const logger = createLogger('HubSpotDeleteAssociation')
9+
10+
export const hubspotDeleteAssociationTool: ToolConfig<
11+
HubSpotDeleteAssociationParams,
12+
HubSpotDeleteAssociationResponse
13+
> = {
14+
id: 'hubspot_delete_association',
15+
name: 'Delete Association in HubSpot',
16+
description: 'Remove all associations between two HubSpot records',
17+
version: '1.0.0',
18+
19+
oauth: {
20+
required: true,
21+
provider: 'hubspot',
22+
},
23+
24+
params: {
25+
accessToken: {
26+
type: 'string',
27+
required: true,
28+
visibility: 'hidden',
29+
description: 'The access token for the HubSpot API',
30+
},
31+
objectType: {
32+
type: 'string',
33+
required: true,
34+
visibility: 'user-or-llm',
35+
description: 'The source object type (e.g., "contacts", "companies", "deals")',
36+
},
37+
objectId: {
38+
type: 'string',
39+
required: true,
40+
visibility: 'user-or-llm',
41+
description: 'The ID of the source record',
42+
},
43+
toObjectType: {
44+
type: 'string',
45+
required: true,
46+
visibility: 'user-or-llm',
47+
description: 'The target object type (e.g., "emails", "notes", "contacts")',
48+
},
49+
toObjectId: {
50+
type: 'string',
51+
required: true,
52+
visibility: 'user-or-llm',
53+
description: 'The ID of the target record',
54+
},
55+
},
56+
57+
request: {
58+
url: (params) => {
59+
const from = `${encodeURIComponent(params.objectType.trim())}/${encodeURIComponent(params.objectId.trim())}`
60+
const to = `${encodeURIComponent(params.toObjectType.trim())}/${encodeURIComponent(params.toObjectId.trim())}`
61+
return `https://api.hubapi.com/crm/v4/objects/${from}/associations/${to}`
62+
},
63+
method: 'DELETE',
64+
headers: (params) => {
65+
if (!params.accessToken) {
66+
throw new Error('Access token is required')
67+
}
68+
return {
69+
Authorization: `Bearer ${params.accessToken}`,
70+
}
71+
},
72+
},
73+
74+
transformResponse: async (response: Response, params) => {
75+
if (!response.ok) {
76+
const data = await response.json().catch(() => ({}))
77+
logger.error('HubSpot API request failed', { data, status: response.status })
78+
throw new Error(data.message || 'Failed to delete association in HubSpot')
79+
}
80+
return {
81+
success: true,
82+
output: {
83+
fromObjectId: params?.objectId ?? '',
84+
toObjectId: params?.toObjectId ?? '',
85+
deleted: true,
86+
success: true,
87+
},
88+
}
89+
},
90+
91+
outputs: {
92+
fromObjectId: { type: 'string', description: 'Source record ID' },
93+
toObjectId: { type: 'string', description: 'Target record ID' },
94+
deleted: { type: 'boolean', description: 'Whether the associations were removed' },
95+
success: { type: 'boolean', description: 'Operation success status' },
96+
},
97+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import { createLogger } from '@sim/logger'
2+
import type {
3+
HubSpotDeleteCompanyParams,
4+
HubSpotDeleteCompanyResponse,
5+
} from '@/tools/hubspot/types'
6+
import type { ToolConfig } from '@/tools/types'
7+
8+
const logger = createLogger('HubSpotDeleteCompany')
9+
10+
export const hubspotDeleteCompanyTool: ToolConfig<
11+
HubSpotDeleteCompanyParams,
12+
HubSpotDeleteCompanyResponse
13+
> = {
14+
id: 'hubspot_delete_company',
15+
name: 'Delete Company from HubSpot',
16+
description: 'Archive a company in HubSpot by ID (moves it to the recycling bin)',
17+
version: '1.0.0',
18+
19+
oauth: {
20+
required: true,
21+
provider: 'hubspot',
22+
},
23+
24+
params: {
25+
accessToken: {
26+
type: 'string',
27+
required: true,
28+
visibility: 'hidden',
29+
description: 'The access token for the HubSpot API',
30+
},
31+
companyId: {
32+
type: 'string',
33+
required: true,
34+
visibility: 'user-or-llm',
35+
description: 'The numeric ID of the company to delete',
36+
},
37+
},
38+
39+
request: {
40+
url: (params) => `https://api.hubapi.com/crm/v3/objects/companies/${params.companyId.trim()}`,
41+
method: 'DELETE',
42+
headers: (params) => {
43+
if (!params.accessToken) {
44+
throw new Error('Access token is required')
45+
}
46+
return {
47+
Authorization: `Bearer ${params.accessToken}`,
48+
}
49+
},
50+
},
51+
52+
transformResponse: async (response: Response, params) => {
53+
if (!response.ok) {
54+
const data = await response.json().catch(() => ({}))
55+
logger.error('HubSpot API request failed', { data, status: response.status })
56+
throw new Error(data.message || 'Failed to delete company from HubSpot')
57+
}
58+
return {
59+
success: true,
60+
output: {
61+
companyId: params?.companyId ?? '',
62+
deleted: true,
63+
success: true,
64+
},
65+
}
66+
},
67+
68+
outputs: {
69+
companyId: { type: 'string', description: 'ID of the deleted company' },
70+
deleted: { type: 'boolean', description: 'Whether the company was archived' },
71+
success: { type: 'boolean', description: 'Operation success status' },
72+
},
73+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import { createLogger } from '@sim/logger'
2+
import type {
3+
HubSpotDeleteContactParams,
4+
HubSpotDeleteContactResponse,
5+
} from '@/tools/hubspot/types'
6+
import type { ToolConfig } from '@/tools/types'
7+
8+
const logger = createLogger('HubSpotDeleteContact')
9+
10+
export const hubspotDeleteContactTool: ToolConfig<
11+
HubSpotDeleteContactParams,
12+
HubSpotDeleteContactResponse
13+
> = {
14+
id: 'hubspot_delete_contact',
15+
name: 'Delete Contact from HubSpot',
16+
description: 'Archive a contact in HubSpot by ID (moves it to the recycling bin)',
17+
version: '1.0.0',
18+
19+
oauth: {
20+
required: true,
21+
provider: 'hubspot',
22+
},
23+
24+
params: {
25+
accessToken: {
26+
type: 'string',
27+
required: true,
28+
visibility: 'hidden',
29+
description: 'The access token for the HubSpot API',
30+
},
31+
contactId: {
32+
type: 'string',
33+
required: true,
34+
visibility: 'user-or-llm',
35+
description: 'The numeric ID of the contact to delete',
36+
},
37+
},
38+
39+
request: {
40+
url: (params) => `https://api.hubapi.com/crm/v3/objects/contacts/${params.contactId.trim()}`,
41+
method: 'DELETE',
42+
headers: (params) => {
43+
if (!params.accessToken) {
44+
throw new Error('Access token is required')
45+
}
46+
return {
47+
Authorization: `Bearer ${params.accessToken}`,
48+
}
49+
},
50+
},
51+
52+
transformResponse: async (response: Response, params) => {
53+
if (!response.ok) {
54+
const data = await response.json().catch(() => ({}))
55+
logger.error('HubSpot API request failed', { data, status: response.status })
56+
throw new Error(data.message || 'Failed to delete contact from HubSpot')
57+
}
58+
return {
59+
success: true,
60+
output: {
61+
contactId: params?.contactId ?? '',
62+
deleted: true,
63+
success: true,
64+
},
65+
}
66+
},
67+
68+
outputs: {
69+
contactId: { type: 'string', description: 'ID of the deleted contact' },
70+
deleted: { type: 'boolean', description: 'Whether the contact was archived' },
71+
success: { type: 'boolean', description: 'Operation success status' },
72+
},
73+
}

0 commit comments

Comments
 (0)