Skip to content

Commit 3ce09f6

Browse files
committed
feat(hubspot): add 27 CRM tools and fix OAuth scope mismatch
1 parent 8800f03 commit 3ce09f6

34 files changed

+3443
-10
lines changed

apps/docs/content/docs/en/tools/hubspot.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -499,7 +499,7 @@ Retrieve all deals from HubSpot account with pagination support
499499

500500
| Parameter | Type | Required | Description |
501501
| --------- | ---- | -------- | ----------- |
502-
| `limit` | string | No | Maximum number of results per page \(max 100, default 100\) |
502+
| `limit` | string | No | Maximum number of results per page \(max 100, default 10\) |
503503
| `after` | string | No | Pagination cursor for next page of results \(from previous response\) |
504504
| `properties` | string | No | Comma-separated list of HubSpot property names to return \(e.g., "dealname,amount,dealstage"\) |
505505
| `associations` | string | No | Comma-separated list of object types to retrieve associated IDs for \(e.g., "contacts,companies"\) |

apps/sim/connectors/hubspot/hubspot.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ export const hubspotConnector: ConnectorConfig = {
185185
'crm.objects.contacts.read',
186186
'crm.objects.companies.read',
187187
'crm.objects.deals.read',
188-
'crm.objects.tickets.read',
188+
'tickets',
189189
],
190190
},
191191

apps/sim/lib/oauth/oauth.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -886,8 +886,6 @@ export const OAUTH_PROVIDERS: Record<string, OAuthProviderConfig> = {
886886
'crm.import',
887887
'crm.lists.read',
888888
'crm.lists.write',
889-
'crm.objects.tickets.read',
890-
'crm.objects.tickets.write',
891889
'tickets',
892890
'oauth',
893891
],
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import { createLogger } from '@sim/logger'
2+
import type {
3+
HubSpotCreateAppointmentParams,
4+
HubSpotCreateAppointmentResponse,
5+
} from '@/tools/hubspot/types'
6+
import { APPOINTMENT_OBJECT_OUTPUT } from '@/tools/hubspot/types'
7+
import type { ToolConfig } from '@/tools/types'
8+
9+
const logger = createLogger('HubSpotCreateAppointment')
10+
11+
export const hubspotCreateAppointmentTool: ToolConfig<
12+
HubSpotCreateAppointmentParams,
13+
HubSpotCreateAppointmentResponse
14+
> = {
15+
id: 'hubspot_create_appointment',
16+
name: 'Create Appointment in HubSpot',
17+
description: 'Create a new appointment in HubSpot',
18+
version: '1.0.0',
19+
20+
oauth: {
21+
required: true,
22+
provider: 'hubspot',
23+
},
24+
25+
params: {
26+
accessToken: {
27+
type: 'string',
28+
required: true,
29+
visibility: 'hidden',
30+
description: 'The access token for the HubSpot API',
31+
},
32+
properties: {
33+
type: 'object',
34+
required: true,
35+
visibility: 'user-or-llm',
36+
description:
37+
'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"})',
38+
},
39+
associations: {
40+
type: 'array',
41+
required: false,
42+
visibility: 'user-or-llm',
43+
description:
44+
'Array of associations to create with the appointment as JSON. Each object should have "to.id" and "types" array with "associationCategory" and "associationTypeId"',
45+
},
46+
},
47+
48+
request: {
49+
url: () => 'https://api.hubapi.com/crm/v3/objects/appointments',
50+
method: 'POST',
51+
headers: (params) => {
52+
if (!params.accessToken) {
53+
throw new Error('Access token is required')
54+
}
55+
return {
56+
Authorization: `Bearer ${params.accessToken}`,
57+
'Content-Type': 'application/json',
58+
}
59+
},
60+
body: (params) => {
61+
let properties = params.properties
62+
if (typeof properties === 'string') {
63+
try {
64+
properties = JSON.parse(properties)
65+
} catch (e) {
66+
throw new Error('Invalid JSON format for properties. Please provide a valid JSON object.')
67+
}
68+
}
69+
const body: Record<string, unknown> = { properties }
70+
if (params.associations && params.associations.length > 0) {
71+
body.associations = params.associations
72+
}
73+
return body
74+
},
75+
},
76+
77+
transformResponse: async (response: Response) => {
78+
const data = await response.json()
79+
if (!response.ok) {
80+
logger.error('HubSpot API request failed', { data, status: response.status })
81+
throw new Error(data.message || 'Failed to create appointment in HubSpot')
82+
}
83+
return {
84+
success: true,
85+
output: { appointment: data, appointmentId: data.id, success: true },
86+
}
87+
},
88+
89+
outputs: {
90+
appointment: APPOINTMENT_OBJECT_OUTPUT,
91+
appointmentId: { type: 'string', description: 'The created appointment ID' },
92+
success: { type: 'boolean', description: 'Operation success status' },
93+
},
94+
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import { createLogger } from '@sim/logger'
2+
import type { HubSpotCreateDealParams, HubSpotCreateDealResponse } from '@/tools/hubspot/types'
3+
import { DEAL_OBJECT_OUTPUT } from '@/tools/hubspot/types'
4+
import type { ToolConfig } from '@/tools/types'
5+
6+
const logger = createLogger('HubSpotCreateDeal')
7+
8+
export const hubspotCreateDealTool: ToolConfig<HubSpotCreateDealParams, HubSpotCreateDealResponse> =
9+
{
10+
id: 'hubspot_create_deal',
11+
name: 'Create Deal in HubSpot',
12+
description: 'Create a new deal in HubSpot. Requires at least a dealname property',
13+
version: '1.0.0',
14+
15+
oauth: {
16+
required: true,
17+
provider: 'hubspot',
18+
},
19+
20+
params: {
21+
accessToken: {
22+
type: 'string',
23+
required: true,
24+
visibility: 'hidden',
25+
description: 'The access token for the HubSpot API',
26+
},
27+
properties: {
28+
type: 'object',
29+
required: true,
30+
visibility: 'user-or-llm',
31+
description:
32+
'Deal properties as JSON object. Must include dealname (e.g., {"dealname": "New Deal", "amount": "5000", "dealstage": "appointmentscheduled"})',
33+
},
34+
associations: {
35+
type: 'array',
36+
required: false,
37+
visibility: 'user-or-llm',
38+
description:
39+
'Array of associations to create with the deal as JSON. Each object should have "to.id" and "types" array with "associationCategory" and "associationTypeId"',
40+
},
41+
},
42+
43+
request: {
44+
url: () => 'https://api.hubapi.com/crm/v3/objects/deals',
45+
method: 'POST',
46+
headers: (params) => {
47+
if (!params.accessToken) {
48+
throw new Error('Access token is required')
49+
}
50+
return {
51+
Authorization: `Bearer ${params.accessToken}`,
52+
'Content-Type': 'application/json',
53+
}
54+
},
55+
body: (params) => {
56+
let properties = params.properties
57+
if (typeof properties === 'string') {
58+
try {
59+
properties = JSON.parse(properties)
60+
} catch (e) {
61+
throw new Error(
62+
'Invalid JSON format for properties. Please provide a valid JSON object.'
63+
)
64+
}
65+
}
66+
const body: Record<string, unknown> = { properties }
67+
if (params.associations && params.associations.length > 0) {
68+
body.associations = params.associations
69+
}
70+
return body
71+
},
72+
},
73+
74+
transformResponse: async (response: Response) => {
75+
const data = await response.json()
76+
if (!response.ok) {
77+
logger.error('HubSpot API request failed', { data, status: response.status })
78+
throw new Error(data.message || 'Failed to create deal in HubSpot')
79+
}
80+
return {
81+
success: true,
82+
output: { deal: data, dealId: data.id, success: true },
83+
}
84+
},
85+
86+
outputs: {
87+
deal: DEAL_OBJECT_OUTPUT,
88+
dealId: { type: 'string', description: 'The created deal ID' },
89+
success: { type: 'boolean', description: 'Operation success status' },
90+
},
91+
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import { createLogger } from '@sim/logger'
2+
import type {
3+
HubSpotCreateLineItemParams,
4+
HubSpotCreateLineItemResponse,
5+
} from '@/tools/hubspot/types'
6+
import { LINE_ITEM_OBJECT_OUTPUT } from '@/tools/hubspot/types'
7+
import type { ToolConfig } from '@/tools/types'
8+
9+
const logger = createLogger('HubSpotCreateLineItem')
10+
11+
export const hubspotCreateLineItemTool: ToolConfig<
12+
HubSpotCreateLineItemParams,
13+
HubSpotCreateLineItemResponse
14+
> = {
15+
id: 'hubspot_create_line_item',
16+
name: 'Create Line Item in HubSpot',
17+
description: 'Create a new line item in HubSpot. Requires at least a name property',
18+
version: '1.0.0',
19+
20+
oauth: {
21+
required: true,
22+
provider: 'hubspot',
23+
},
24+
25+
params: {
26+
accessToken: {
27+
type: 'string',
28+
required: true,
29+
visibility: 'hidden',
30+
description: 'The access token for the HubSpot API',
31+
},
32+
properties: {
33+
type: 'object',
34+
required: true,
35+
visibility: 'user-or-llm',
36+
description:
37+
'Line item properties as JSON object (e.g., {"name": "Product A", "quantity": "2", "price": "50.00", "hs_sku": "SKU-001"})',
38+
},
39+
associations: {
40+
type: 'array',
41+
required: false,
42+
visibility: 'user-or-llm',
43+
description:
44+
'Array of associations to create with the line item as JSON. Each object should have "to.id" and "types" array with "associationCategory" and "associationTypeId"',
45+
},
46+
},
47+
48+
request: {
49+
url: () => 'https://api.hubapi.com/crm/v3/objects/line_items',
50+
method: 'POST',
51+
headers: (params) => {
52+
if (!params.accessToken) {
53+
throw new Error('Access token is required')
54+
}
55+
return {
56+
Authorization: `Bearer ${params.accessToken}`,
57+
'Content-Type': 'application/json',
58+
}
59+
},
60+
body: (params) => {
61+
let properties = params.properties
62+
if (typeof properties === 'string') {
63+
try {
64+
properties = JSON.parse(properties)
65+
} catch (e) {
66+
throw new Error('Invalid JSON format for properties. Please provide a valid JSON object.')
67+
}
68+
}
69+
const body: Record<string, unknown> = { properties }
70+
if (params.associations && params.associations.length > 0) {
71+
body.associations = params.associations
72+
}
73+
return body
74+
},
75+
},
76+
77+
transformResponse: async (response: Response) => {
78+
const data = await response.json()
79+
if (!response.ok) {
80+
logger.error('HubSpot API request failed', { data, status: response.status })
81+
throw new Error(data.message || 'Failed to create line item in HubSpot')
82+
}
83+
return {
84+
success: true,
85+
output: { lineItem: data, lineItemId: data.id, success: true },
86+
}
87+
},
88+
89+
outputs: {
90+
lineItem: LINE_ITEM_OBJECT_OUTPUT,
91+
lineItemId: { type: 'string', description: 'The created line item ID' },
92+
success: { type: 'boolean', description: 'Operation success status' },
93+
},
94+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import { createLogger } from '@sim/logger'
2+
import type { HubSpotCreateListParams, HubSpotCreateListResponse } from '@/tools/hubspot/types'
3+
import { LIST_OUTPUT_PROPERTIES } from '@/tools/hubspot/types'
4+
import type { ToolConfig } from '@/tools/types'
5+
6+
const logger = createLogger('HubSpotCreateList')
7+
8+
export const hubspotCreateListTool: ToolConfig<HubSpotCreateListParams, HubSpotCreateListResponse> =
9+
{
10+
id: 'hubspot_create_list',
11+
name: 'Create List in HubSpot',
12+
description:
13+
'Create a new list in HubSpot. Specify the object type and processing type (MANUAL or DYNAMIC)',
14+
version: '1.0.0',
15+
16+
oauth: {
17+
required: true,
18+
provider: 'hubspot',
19+
},
20+
21+
params: {
22+
accessToken: {
23+
type: 'string',
24+
required: true,
25+
visibility: 'hidden',
26+
description: 'The access token for the HubSpot API',
27+
},
28+
name: {
29+
type: 'string',
30+
required: true,
31+
visibility: 'user-or-llm',
32+
description: 'Name of the list',
33+
},
34+
objectTypeId: {
35+
type: 'string',
36+
required: true,
37+
visibility: 'user-or-llm',
38+
description: 'Object type ID (e.g., "0-1" for contacts, "0-2" for companies)',
39+
},
40+
processingType: {
41+
type: 'string',
42+
required: true,
43+
visibility: 'user-or-llm',
44+
description: 'Processing type: "MANUAL" for static lists or "DYNAMIC" for active lists',
45+
},
46+
},
47+
48+
request: {
49+
url: () => 'https://api.hubapi.com/crm/v3/lists',
50+
method: 'POST',
51+
headers: (params) => {
52+
if (!params.accessToken) {
53+
throw new Error('Access token is required')
54+
}
55+
return {
56+
Authorization: `Bearer ${params.accessToken}`,
57+
'Content-Type': 'application/json',
58+
}
59+
},
60+
body: (params) => ({
61+
name: params.name,
62+
objectTypeId: params.objectTypeId,
63+
processingType: params.processingType,
64+
}),
65+
},
66+
67+
transformResponse: async (response: Response) => {
68+
const data = await response.json()
69+
if (!response.ok) {
70+
logger.error('HubSpot API request failed', { data, status: response.status })
71+
throw new Error(data.message || 'Failed to create list in HubSpot')
72+
}
73+
return {
74+
success: true,
75+
output: { list: data, listId: data.listId ?? data.id, success: true },
76+
}
77+
},
78+
79+
outputs: {
80+
list: {
81+
type: 'object',
82+
description: 'HubSpot list',
83+
properties: LIST_OUTPUT_PROPERTIES,
84+
},
85+
listId: { type: 'string', description: 'The created list ID' },
86+
success: { type: 'boolean', description: 'Operation success status' },
87+
},
88+
}

0 commit comments

Comments
 (0)