From fb62e3e0a9ed62347a2ae75a4629a8ec49ddc52e Mon Sep 17 00:00:00 2001 From: waleed Date: Sat, 13 Jun 2026 17:12:00 -0700 Subject: [PATCH 1/3] feat(hubspot): add notes, emails, properties & associations tools - Add 11 tools: get_properties (read property/enum options), notes (create/get/list/search), email engagements (create/get/list/search), and v4 associations (list/create) - Add scopes: crm.objects.notes.read/write, crm.objects.emails.read/write, sales-email-read (required for email engagement content) - Wire new operations into the HubSpot block (subBlocks, conditions, tool mapping, outputs, BlockMeta templates/skills) - Fix pre-existing bugs found in validation: list_marketing_events list URL (/marketing/marketing-events/v3), appointment property names (hs_appointment_*), get_users output shape (CRM envelope), create_list response unwrap, and stringified-associations parsing in create tools - Regenerate integration docs --- apps/docs/components/icons.tsx | 55 +-- .../content/docs/en/integrations/hubspot.mdx | 418 ++++++++++++++++-- apps/sim/blocks/blocks/hubspot.ts | 259 ++++++++++- apps/sim/lib/integrations/integrations.json | 42 +- apps/sim/lib/oauth/oauth.ts | 5 + apps/sim/lib/oauth/utils.ts | 5 + apps/sim/tools/hubspot/create_appointment.ts | 16 +- apps/sim/tools/hubspot/create_association.ts | 141 ++++++ apps/sim/tools/hubspot/create_company.ts | 14 +- apps/sim/tools/hubspot/create_contact.ts | 14 +- apps/sim/tools/hubspot/create_deal.ts | 17 +- apps/sim/tools/hubspot/create_email.ts | 114 +++++ apps/sim/tools/hubspot/create_line_item.ts | 14 +- apps/sim/tools/hubspot/create_list.ts | 6 +- apps/sim/tools/hubspot/create_note.ts | 114 +++++ apps/sim/tools/hubspot/create_ticket.ts | 14 +- apps/sim/tools/hubspot/get_appointment.ts | 2 +- apps/sim/tools/hubspot/get_email.ts | 100 +++++ apps/sim/tools/hubspot/get_note.ts | 99 +++++ apps/sim/tools/hubspot/get_properties.ts | 120 +++++ apps/sim/tools/hubspot/get_users.ts | 16 +- apps/sim/tools/hubspot/index.ts | 11 + apps/sim/tools/hubspot/list_appointments.ts | 2 +- apps/sim/tools/hubspot/list_associations.ts | 121 +++++ apps/sim/tools/hubspot/list_companies.ts | 2 +- apps/sim/tools/hubspot/list_contacts.ts | 2 +- apps/sim/tools/hubspot/list_emails.ts | 117 +++++ .../tools/hubspot/list_marketing_events.ts | 2 +- apps/sim/tools/hubspot/list_notes.ts | 116 +++++ apps/sim/tools/hubspot/search_emails.ts | 169 +++++++ apps/sim/tools/hubspot/search_notes.ts | 169 +++++++ apps/sim/tools/hubspot/types.ts | 395 +++++++++++++++-- apps/sim/tools/hubspot/update_appointment.ts | 2 +- apps/sim/tools/registry.ts | 22 + 34 files changed, 2577 insertions(+), 138 deletions(-) create mode 100644 apps/sim/tools/hubspot/create_association.ts create mode 100644 apps/sim/tools/hubspot/create_email.ts create mode 100644 apps/sim/tools/hubspot/create_note.ts create mode 100644 apps/sim/tools/hubspot/get_email.ts create mode 100644 apps/sim/tools/hubspot/get_note.ts create mode 100644 apps/sim/tools/hubspot/get_properties.ts create mode 100644 apps/sim/tools/hubspot/list_associations.ts create mode 100644 apps/sim/tools/hubspot/list_emails.ts create mode 100644 apps/sim/tools/hubspot/list_notes.ts create mode 100644 apps/sim/tools/hubspot/search_emails.ts create mode 100644 apps/sim/tools/hubspot/search_notes.ts diff --git a/apps/docs/components/icons.tsx b/apps/docs/components/icons.tsx index 36c53d53417..8ea1529b1e8 100644 --- a/apps/docs/components/icons.tsx +++ b/apps/docs/components/icons.tsx @@ -3704,44 +3704,23 @@ export function QdrantIcon(props: SVGProps) { export function QuartrIcon(props: SVGProps) { return ( - - - - - - - - - - - + + + + + + + + ) } diff --git a/apps/docs/content/docs/en/integrations/hubspot.mdx b/apps/docs/content/docs/en/integrations/hubspot.mdx index f21d38fbeee..2004d368eb2 100644 --- a/apps/docs/content/docs/en/integrations/hubspot.mdx +++ b/apps/docs/content/docs/en/integrations/hubspot.mdx @@ -42,20 +42,21 @@ Retrieve all users from HubSpot account | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `limit` | string | No | Number of results to return \(default: 100, max: 100\) | +| `limit` | string | No | Number of results to return \(default: 10, max: 100\) | | `after` | string | No | Pagination cursor for next page of results \(from previous response\) | +| `properties` | string | No | Comma-separated list of HubSpot user property names to return \(e.g., "hs_email,hs_given_name,hs_family_name"\) | #### Output | Parameter | Type | Description | | --------- | ---- | ----------- | -| `users` | array | Array of HubSpot user objects | -| ↳ `id` | string | User ID | -| ↳ `email` | string | User email address | -| ↳ `roleId` | string | User role ID | -| ↳ `primaryTeamId` | string | Primary team ID | -| ↳ `secondaryTeamIds` | array | Secondary team IDs | -| ↳ `superAdmin` | boolean | Whether user is a super admin | +| `users` | array | Array of HubSpot CRM records | +| ↳ `id` | string | Unique record ID \(hs_object_id\) | +| ↳ `createdAt` | string | Record creation timestamp \(ISO 8601\) | +| ↳ `updatedAt` | string | Record last updated timestamp \(ISO 8601\) | +| ↳ `archived` | boolean | Whether the record is archived | +| ↳ `properties` | object | Record properties | +| ↳ `associations` | object | Associated records | | `paging` | object | Pagination information for fetching more results | | ↳ `after` | string | Cursor for next page of results | | ↳ `link` | string | Link to next page | @@ -70,7 +71,7 @@ Retrieve all contacts 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., "email,firstname,lastname,phone"\) | | `associations` | string | No | Comma-separated list of object types to retrieve associated IDs for \(e.g., "companies,deals"\) | @@ -285,7 +286,7 @@ Retrieve all companies 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., "name,domain,industry"\) | | `associations` | string | No | Comma-separated list of object types to retrieve associated IDs for \(e.g., "contacts,deals"\) | @@ -570,7 +571,7 @@ Retrieve a single deal by ID from HubSpot ### `hubspot_create_deal` -Create a new deal in HubSpot. Requires at least a dealname property +Create a new deal in HubSpot with the given properties (e.g., dealname, amount, dealstage) #### Input @@ -837,6 +838,357 @@ Search for tickets in HubSpot using filters, sorting, and queries | `total` | number | Total number of matching tickets | | `success` | boolean | Operation success status | +### `hubspot_list_notes` + +Retrieve all notes from HubSpot account with pagination support + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `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., "hs_note_body,hs_timestamp,hubspot_owner_id"\) | +| `associations` | string | No | Comma-separated list of object types to retrieve associated IDs for \(e.g., "contacts,companies,deals"\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `notes` | array | Array of HubSpot note records | +| ↳ `hs_note_body` | string | Note text/body \(supports rich text/HTML\) | +| ↳ `hs_timestamp` | string | Note activity time \(ISO 8601\) | +| ↳ `hubspot_owner_id` | string | HubSpot owner ID | +| ↳ `hs_attachment_ids` | string | Semicolon-separated IDs of files attached to the note | +| ↳ `hs_object_id` | string | HubSpot object ID \(same as record ID\) | +| ↳ `hs_createdate` | string | Note creation date \(ISO 8601\) | +| ↳ `hs_lastmodifieddate` | string | Last modified date \(ISO 8601\) | +| `paging` | object | Pagination information for fetching more results | +| ↳ `after` | string | Cursor for next page of results | +| ↳ `link` | string | Link to next page | +| `metadata` | object | Response metadata | +| ↳ `totalReturned` | number | Number of records returned in this response | +| ↳ `hasMore` | boolean | Whether more records are available | +| `success` | boolean | Operation success status | + +### `hubspot_get_note` + +Retrieve a single note by ID from HubSpot + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `noteId` | string | Yes | The HubSpot note ID to retrieve | +| `properties` | string | No | Comma-separated list of HubSpot property names to return \(e.g., "hs_note_body,hs_timestamp,hubspot_owner_id"\) | +| `associations` | string | No | Comma-separated list of object types to retrieve associated IDs for \(e.g., "contacts,companies,deals"\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `note` | object | HubSpot note record | +| ↳ `hs_note_body` | string | Note text/body \(supports rich text/HTML\) | +| ↳ `hs_timestamp` | string | Note activity time \(ISO 8601\) | +| ↳ `hubspot_owner_id` | string | HubSpot owner ID | +| ↳ `hs_attachment_ids` | string | Semicolon-separated IDs of files attached to the note | +| ↳ `hs_object_id` | string | HubSpot object ID \(same as record ID\) | +| ↳ `hs_createdate` | string | Note creation date \(ISO 8601\) | +| ↳ `hs_lastmodifieddate` | string | Last modified date \(ISO 8601\) | +| `noteId` | string | The retrieved note ID | +| `success` | boolean | Operation success status | + +### `hubspot_create_note` + +Log a note in HubSpot and optionally associate it with contacts, companies, or deals. Requires hs_timestamp and hs_note_body properties + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `properties` | object | Yes | Note properties as JSON object. Must include "hs_timestamp" \(ISO 8601 activity time\) and "hs_note_body" \(the note text\). e.g., \{"hs_timestamp": "2026-06-13T00:00:00Z", "hs_note_body": "Followed up via phone"\} | +| `associations` | array | No | Array of associations as JSON. Each object has "to.id" \(record ID\) and "types" array with "associationCategory" \("HUBSPOT_DEFINED"\) and "associationTypeId" \(202 = note→contact, 190 = note→company, 214 = note→deal\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `note` | object | HubSpot note record | +| ↳ `hs_note_body` | string | Note text/body \(supports rich text/HTML\) | +| ↳ `hs_timestamp` | string | Note activity time \(ISO 8601\) | +| ↳ `hubspot_owner_id` | string | HubSpot owner ID | +| ↳ `hs_attachment_ids` | string | Semicolon-separated IDs of files attached to the note | +| ↳ `hs_object_id` | string | HubSpot object ID \(same as record ID\) | +| ↳ `hs_createdate` | string | Note creation date \(ISO 8601\) | +| ↳ `hs_lastmodifieddate` | string | Last modified date \(ISO 8601\) | +| `noteId` | string | The created note ID | +| `success` | boolean | Operation success status | + +### `hubspot_search_notes` + +Search for notes in HubSpot using filters, sorting, and queries + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `filterGroups` | array | No | 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` | array | No | Array of sort objects as JSON with "propertyName" and "direction" \("ASCENDING" or "DESCENDING"\) | +| `query` | string | No | Search query string to match against note text fields | +| `properties` | array | No | Array of HubSpot property names to return \(e.g., \["hs_note_body", "hs_timestamp", "hubspot_owner_id"\]\) | +| `limit` | number | No | Maximum number of results to return \(max 100\) | +| `after` | string | No | Pagination cursor for next page \(from previous response\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `notes` | array | Array of HubSpot note records | +| ↳ `hs_note_body` | string | Note text/body \(supports rich text/HTML\) | +| ↳ `hs_timestamp` | string | Note activity time \(ISO 8601\) | +| ↳ `hubspot_owner_id` | string | HubSpot owner ID | +| ↳ `hs_attachment_ids` | string | Semicolon-separated IDs of files attached to the note | +| ↳ `hs_object_id` | string | HubSpot object ID \(same as record ID\) | +| ↳ `hs_createdate` | string | Note creation date \(ISO 8601\) | +| ↳ `hs_lastmodifieddate` | string | Last modified date \(ISO 8601\) | +| `paging` | object | Pagination information for fetching more results | +| ↳ `after` | string | Cursor for next page of results | +| ↳ `link` | string | Link to next page | +| `metadata` | object | Response metadata | +| ↳ `totalReturned` | number | Number of records returned in this response | +| ↳ `hasMore` | boolean | Whether more records are available | +| `total` | number | Total number of matching notes | +| `success` | boolean | Operation success status | + +### `hubspot_list_emails` + +Retrieve all email engagements from HubSpot account with pagination support + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `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., "hs_email_subject,hs_email_text,hs_timestamp"\) | +| `associations` | string | No | Comma-separated list of object types to retrieve associated IDs for \(e.g., "contacts,companies,deals"\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `emails` | array | Array of HubSpot email engagement records | +| ↳ `hs_timestamp` | string | Email activity time \(ISO 8601\) | +| ↳ `hs_email_direction` | string | Direction \(EMAIL = outgoing, INCOMING_EMAIL, FORWARDED_EMAIL\) | +| ↳ `hs_email_status` | string | Send status \(SENT, SENDING, SCHEDULED, FAILED, BOUNCED\) | +| ↳ `hs_email_subject` | string | Email subject line | +| ↳ `hs_email_text` | string | Plain-text email body | +| ↳ `hs_email_html` | string | HTML email body | +| ↳ `hs_email_headers` | string | JSON-encoded from/to/cc/bcc headers | +| ↳ `hs_email_from_email` | string | Sender email address | +| ↳ `hs_email_to_email` | string | Recipient email address\(es\) | +| ↳ `hubspot_owner_id` | string | HubSpot owner ID | +| ↳ `hs_object_id` | string | HubSpot object ID \(same as record ID\) | +| ↳ `hs_createdate` | string | Email creation date \(ISO 8601\) | +| ↳ `hs_lastmodifieddate` | string | Last modified date \(ISO 8601\) | +| `paging` | object | Pagination information for fetching more results | +| ↳ `after` | string | Cursor for next page of results | +| ↳ `link` | string | Link to next page | +| `metadata` | object | Response metadata | +| ↳ `totalReturned` | number | Number of records returned in this response | +| ↳ `hasMore` | boolean | Whether more records are available | +| `success` | boolean | Operation success status | + +### `hubspot_get_email` + +Retrieve a single email engagement by ID from HubSpot (content requires the sales-email-read scope) + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `emailId` | string | Yes | The HubSpot email engagement ID to retrieve | +| `properties` | string | No | Comma-separated list of HubSpot property names to return \(e.g., "hs_email_subject,hs_email_text,hs_timestamp"\) | +| `associations` | string | No | Comma-separated list of object types to retrieve associated IDs for \(e.g., "contacts,companies,deals"\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `email` | object | HubSpot email engagement record | +| ↳ `hs_timestamp` | string | Email activity time \(ISO 8601\) | +| ↳ `hs_email_direction` | string | Direction \(EMAIL = outgoing, INCOMING_EMAIL, FORWARDED_EMAIL\) | +| ↳ `hs_email_status` | string | Send status \(SENT, SENDING, SCHEDULED, FAILED, BOUNCED\) | +| ↳ `hs_email_subject` | string | Email subject line | +| ↳ `hs_email_text` | string | Plain-text email body | +| ↳ `hs_email_html` | string | HTML email body | +| ↳ `hs_email_headers` | string | JSON-encoded from/to/cc/bcc headers | +| ↳ `hs_email_from_email` | string | Sender email address | +| ↳ `hs_email_to_email` | string | Recipient email address\(es\) | +| ↳ `hubspot_owner_id` | string | HubSpot owner ID | +| ↳ `hs_object_id` | string | HubSpot object ID \(same as record ID\) | +| ↳ `hs_createdate` | string | Email creation date \(ISO 8601\) | +| ↳ `hs_lastmodifieddate` | string | Last modified date \(ISO 8601\) | +| `emailId` | string | The retrieved email engagement ID | +| `success` | boolean | Operation success status | + +### `hubspot_create_email` + +Log an email engagement in HubSpot and optionally associate it with contacts. Requires the hs_timestamp property + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `properties` | object | Yes | Email properties as JSON object. Must include "hs_timestamp" \(ISO 8601\). Common fields: "hs_email_direction" \(EMAIL, INCOMING_EMAIL, FORWARDED_EMAIL\), "hs_email_status" \(SENT, SENDING, SCHEDULED, FAILED, BOUNCED\), "hs_email_subject", "hs_email_text", "hs_email_html", "hs_email_headers" \(JSON string\) | +| `associations` | array | No | Array of associations as JSON. Each object has "to.id" \(record ID\) and "types" array with "associationCategory" \("HUBSPOT_DEFINED"\) and "associationTypeId" \(198 = email→contact\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `email` | object | HubSpot email engagement record | +| ↳ `hs_timestamp` | string | Email activity time \(ISO 8601\) | +| ↳ `hs_email_direction` | string | Direction \(EMAIL = outgoing, INCOMING_EMAIL, FORWARDED_EMAIL\) | +| ↳ `hs_email_status` | string | Send status \(SENT, SENDING, SCHEDULED, FAILED, BOUNCED\) | +| ↳ `hs_email_subject` | string | Email subject line | +| ↳ `hs_email_text` | string | Plain-text email body | +| ↳ `hs_email_html` | string | HTML email body | +| ↳ `hs_email_headers` | string | JSON-encoded from/to/cc/bcc headers | +| ↳ `hs_email_from_email` | string | Sender email address | +| ↳ `hs_email_to_email` | string | Recipient email address\(es\) | +| ↳ `hubspot_owner_id` | string | HubSpot owner ID | +| ↳ `hs_object_id` | string | HubSpot object ID \(same as record ID\) | +| ↳ `hs_createdate` | string | Email creation date \(ISO 8601\) | +| ↳ `hs_lastmodifieddate` | string | Last modified date \(ISO 8601\) | +| `emailId` | string | The created email engagement ID | +| `success` | boolean | Operation success status | + +### `hubspot_search_emails` + +Search for email engagements in HubSpot using filters, sorting, and queries + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `filterGroups` | array | No | 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` | array | No | Array of sort objects as JSON with "propertyName" and "direction" \("ASCENDING" or "DESCENDING"\) | +| `query` | string | No | Search query string to match against email text fields | +| `properties` | array | No | Array of HubSpot property names to return \(e.g., \["hs_email_subject", "hs_email_text", "hs_timestamp"\]\) | +| `limit` | number | No | Maximum number of results to return \(max 100\) | +| `after` | string | No | Pagination cursor for next page \(from previous response\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `emails` | array | Array of HubSpot email engagement records | +| ↳ `hs_timestamp` | string | Email activity time \(ISO 8601\) | +| ↳ `hs_email_direction` | string | Direction \(EMAIL = outgoing, INCOMING_EMAIL, FORWARDED_EMAIL\) | +| ↳ `hs_email_status` | string | Send status \(SENT, SENDING, SCHEDULED, FAILED, BOUNCED\) | +| ↳ `hs_email_subject` | string | Email subject line | +| ↳ `hs_email_text` | string | Plain-text email body | +| ↳ `hs_email_html` | string | HTML email body | +| ↳ `hs_email_headers` | string | JSON-encoded from/to/cc/bcc headers | +| ↳ `hs_email_from_email` | string | Sender email address | +| ↳ `hs_email_to_email` | string | Recipient email address\(es\) | +| ↳ `hubspot_owner_id` | string | HubSpot owner ID | +| ↳ `hs_object_id` | string | HubSpot object ID \(same as record ID\) | +| ↳ `hs_createdate` | string | Email creation date \(ISO 8601\) | +| ↳ `hs_lastmodifieddate` | string | Last modified date \(ISO 8601\) | +| `paging` | object | Pagination information for fetching more results | +| ↳ `after` | string | Cursor for next page of results | +| ↳ `link` | string | Link to next page | +| `metadata` | object | Response metadata | +| ↳ `totalReturned` | number | Number of records returned in this response | +| ↳ `hasMore` | boolean | Whether more records are available | +| `total` | number | Total number of matching emails | +| `success` | boolean | Operation success status | + +### `hubspot_get_properties` + +Read property definitions and their enumeration (picklist) options for a HubSpot object type, e.g. the values for lifecyclestage or hs_lead_status on contacts + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `objectType` | string | Yes | Object type to read properties for \(e.g., "contacts", "companies", "deals", "tickets", "line_items", "quotes"\) | +| `propertyName` | string | No | Internal name of a single property to retrieve \(e.g., "hs_lead_status"\). Omit to return all properties for the object type | +| `archived` | boolean | No | Whether to return only archived properties \(default false\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `properties` | array | Array of HubSpot property definitions | +| ↳ `label` | string | Human-readable option label | +| ↳ `value` | string | Internal value used when setting the property | +| ↳ `displayOrder` | number | Display order \(-1 sorts last\) | +| ↳ `hidden` | boolean | Whether the option is hidden in the HubSpot UI | +| ↳ `description` | string | Option description | +| `metadata` | object | Response metadata | +| ↳ `totalReturned` | number | Number of property definitions returned | +| ↳ `objectType` | string | Object type the properties belong to | +| `success` | boolean | Operation success status | + +### `hubspot_list_associations` + +List records of one object type associated with a given record, e.g. all emails or notes logged on a contact + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `objectType` | string | Yes | Source object type \(e.g., "contacts", "companies", "deals"\) | +| `objectId` | string | Yes | ID of the source record | +| `toObjectType` | string | Yes | Target object type to list associations to \(e.g., "emails", "notes", "deals"\) | +| `limit` | string | No | Maximum number of associated records per page \(default 500\) | +| `after` | string | No | Pagination cursor for next page \(from previous response\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `results` | array | Array of associated records | +| ↳ `toObjectId` | string | ID of the associated \(target\) record | +| ↳ `associationTypes` | array | Association types linking the two records | +| ↳ `category` | string | Association category \(HUBSPOT_DEFINED, USER_DEFINED, INTEGRATOR_DEFINED\) | +| ↳ `typeId` | number | Association type ID | +| ↳ `label` | string | Association label | +| `paging` | object | Pagination information for fetching more results | +| ↳ `after` | string | Cursor for next page of results | +| ↳ `link` | string | Link to next page | +| `metadata` | object | Response metadata | +| ↳ `totalReturned` | number | Number of records returned in this response | +| ↳ `hasMore` | boolean | Whether more records are available | +| `success` | boolean | Operation success status | + +### `hubspot_create_association` + +Associate two HubSpot records. Creates the default (unlabeled) association unless an association type ID is provided + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `objectType` | string | Yes | Source object type \(e.g., "emails", "notes", "contacts"\) | +| `objectId` | string | Yes | ID of the source record | +| `toObjectType` | string | Yes | Target object type to associate to \(e.g., "contacts", "companies", "deals"\) | +| `toObjectId` | string | Yes | ID of the target record | +| `associationCategory` | string | No | Association category for a labeled association \(HUBSPOT_DEFINED, USER_DEFINED, INTEGRATOR_DEFINED\). Defaults to HUBSPOT_DEFINED when an association type ID is provided | +| `associationTypeId` | number | No | Specific association type ID for a labeled association. Omit to create the default association for the object pair | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `fromObjectId` | string | ID of the source record | +| `toObjectId` | string | ID of the associated target record | +| `labels` | array | Association labels \(empty for default associations\) | +| `success` | boolean | Operation success status | + ### `hubspot_list_line_items` Retrieve all line items from HubSpot account with pagination support @@ -1046,7 +1398,7 @@ Retrieve all appointments from HubSpot account with pagination support | --------- | ---- | -------- | ----------- | | `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., "hs_meeting_title,hs_meeting_start_time"\) | +| `properties` | string | No | Comma-separated list of HubSpot property names to return \(e.g., "hs_appointment_name,hs_appointment_start"\) | | `associations` | string | No | Comma-separated list of object types to retrieve associated IDs for \(e.g., "contacts,companies"\) | #### Output @@ -1054,11 +1406,10 @@ Retrieve all appointments from HubSpot account with pagination support | Parameter | Type | Description | | --------- | ---- | ----------- | | `appointments` | array | Array of HubSpot appointment records | -| ↳ `hs_appointment_type` | string | Appointment type | -| ↳ `hs_meeting_title` | string | Meeting title | -| ↳ `hs_meeting_start_time` | string | Start time \(ISO 8601\) | -| ↳ `hs_meeting_end_time` | string | End time \(ISO 8601\) | -| ↳ `hs_meeting_location` | string | Meeting location | +| ↳ `hs_appointment_name` | string | Appointment title/name | +| ↳ `hs_appointment_start` | string | Start time \(ISO 8601\) | +| ↳ `hs_appointment_end` | string | End time \(ISO 8601\) | +| ↳ `hs_appointment_status` | string | Appointment status \(e.g., SCHEDULED\) | | ↳ `hubspot_owner_id` | string | HubSpot owner ID | | ↳ `hs_object_id` | string | HubSpot object ID \(same as record ID\) | | ↳ `hs_createdate` | string | Creation date \(ISO 8601\) | @@ -1081,7 +1432,7 @@ Retrieve a single appointment by ID from HubSpot | --------- | ---- | -------- | ----------- | | `appointmentId` | string | Yes | The HubSpot appointment ID to retrieve | | `idProperty` | string | No | Property to use as unique identifier. If not specified, uses record ID | -| `properties` | string | No | Comma-separated list of HubSpot property names to return \(e.g., "hs_meeting_title,hs_meeting_start_time"\) | +| `properties` | string | No | Comma-separated list of HubSpot property names to return \(e.g., "hs_appointment_name,hs_appointment_start"\) | | `associations` | string | No | Comma-separated list of object types to retrieve associated IDs for \(e.g., "contacts,companies"\) | #### Output @@ -1089,11 +1440,10 @@ Retrieve a single appointment by ID from HubSpot | Parameter | Type | Description | | --------- | ---- | ----------- | | `appointment` | object | HubSpot appointment record | -| ↳ `hs_appointment_type` | string | Appointment type | -| ↳ `hs_meeting_title` | string | Meeting title | -| ↳ `hs_meeting_start_time` | string | Start time \(ISO 8601\) | -| ↳ `hs_meeting_end_time` | string | End time \(ISO 8601\) | -| ↳ `hs_meeting_location` | string | Meeting location | +| ↳ `hs_appointment_name` | string | Appointment title/name | +| ↳ `hs_appointment_start` | string | Start time \(ISO 8601\) | +| ↳ `hs_appointment_end` | string | End time \(ISO 8601\) | +| ↳ `hs_appointment_status` | string | Appointment status \(e.g., SCHEDULED\) | | ↳ `hubspot_owner_id` | string | HubSpot owner ID | | ↳ `hs_object_id` | string | HubSpot object ID \(same as record ID\) | | ↳ `hs_createdate` | string | Creation date \(ISO 8601\) | @@ -1109,7 +1459,7 @@ Create a new appointment in HubSpot | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `properties` | object | Yes | 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"\}\) | +| `properties` | object | Yes | 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"\}\) | | `associations` | array | No | Array of associations to create with the appointment as JSON. Each object should have "to.id" and "types" array with "associationCategory" and "associationTypeId" | #### Output @@ -1117,11 +1467,10 @@ Create a new appointment in HubSpot | Parameter | Type | Description | | --------- | ---- | ----------- | | `appointment` | object | HubSpot appointment record | -| ↳ `hs_appointment_type` | string | Appointment type | -| ↳ `hs_meeting_title` | string | Meeting title | -| ↳ `hs_meeting_start_time` | string | Start time \(ISO 8601\) | -| ↳ `hs_meeting_end_time` | string | End time \(ISO 8601\) | -| ↳ `hs_meeting_location` | string | Meeting location | +| ↳ `hs_appointment_name` | string | Appointment title/name | +| ↳ `hs_appointment_start` | string | Start time \(ISO 8601\) | +| ↳ `hs_appointment_end` | string | End time \(ISO 8601\) | +| ↳ `hs_appointment_status` | string | Appointment status \(e.g., SCHEDULED\) | | ↳ `hubspot_owner_id` | string | HubSpot owner ID | | ↳ `hs_object_id` | string | HubSpot object ID \(same as record ID\) | | ↳ `hs_createdate` | string | Creation date \(ISO 8601\) | @@ -1139,18 +1488,17 @@ Update an existing appointment in HubSpot by ID | --------- | ---- | -------- | ----------- | | `appointmentId` | string | Yes | The HubSpot appointment ID to update | | `idProperty` | string | No | Property to use as unique identifier. If not specified, uses record ID | -| `properties` | object | Yes | Appointment properties to update as JSON object \(e.g., \{"hs_meeting_title": "Updated Call", "hs_meeting_location": "Zoom"\}\) | +| `properties` | object | Yes | Appointment properties to update as JSON object \(e.g., \{"hs_appointment_name": "Updated Call", "hs_appointment_start": "2024-01-15T10:00:00Z"\}\) | #### Output | Parameter | Type | Description | | --------- | ---- | ----------- | | `appointment` | object | HubSpot appointment record | -| ↳ `hs_appointment_type` | string | Appointment type | -| ↳ `hs_meeting_title` | string | Meeting title | -| ↳ `hs_meeting_start_time` | string | Start time \(ISO 8601\) | -| ↳ `hs_meeting_end_time` | string | End time \(ISO 8601\) | -| ↳ `hs_meeting_location` | string | Meeting location | +| ↳ `hs_appointment_name` | string | Appointment title/name | +| ↳ `hs_appointment_start` | string | Start time \(ISO 8601\) | +| ↳ `hs_appointment_end` | string | End time \(ISO 8601\) | +| ↳ `hs_appointment_status` | string | Appointment status \(e.g., SCHEDULED\) | | ↳ `hubspot_owner_id` | string | HubSpot owner ID | | ↳ `hs_object_id` | string | HubSpot object ID \(same as record ID\) | | ↳ `hs_createdate` | string | Creation date \(ISO 8601\) | diff --git a/apps/sim/blocks/blocks/hubspot.ts b/apps/sim/blocks/blocks/hubspot.ts index 90d730fb5dc..6eb49dd6eb9 100644 --- a/apps/sim/blocks/blocks/hubspot.ts +++ b/apps/sim/blocks/blocks/hubspot.ts @@ -40,6 +40,15 @@ export const HubSpotBlock: BlockConfig = { { label: 'Create Ticket', id: 'create_ticket' }, { label: 'Update Ticket', id: 'update_ticket' }, { label: 'Search Tickets', id: 'search_tickets' }, + { label: 'Get Notes', id: 'get_notes' }, + { label: 'Create Note', id: 'create_note' }, + { label: 'Search Notes', id: 'search_notes' }, + { label: 'Get Emails', id: 'get_emails' }, + { label: 'Create Email', id: 'create_email' }, + { label: 'Search Emails', id: 'search_emails' }, + { label: 'Get Properties', id: 'get_properties' }, + { label: 'List Associations', id: 'list_associations' }, + { label: 'Create Association', id: 'create_association' }, { label: 'Get Line Items', id: 'get_line_items' }, { label: 'Create Line Item', id: 'create_line_item' }, { label: 'Update Line Item', id: 'update_line_item' }, @@ -136,6 +145,95 @@ export const HubSpotBlock: BlockConfig = { condition: { field: 'operation', value: 'update_ticket' }, required: true, }, + { + id: 'noteId', + title: 'Note ID', + type: 'short-input', + placeholder: 'Leave empty to list all notes', + condition: { field: 'operation', value: 'get_notes' }, + }, + { + id: 'emailId', + title: 'Email ID', + type: 'short-input', + placeholder: 'Leave empty to list all emails', + condition: { field: 'operation', value: 'get_emails' }, + }, + { + id: 'objectType', + title: 'Object Type', + type: 'short-input', + placeholder: 'e.g., "contacts", "companies", "deals", "tickets"', + condition: { + field: 'operation', + value: ['get_properties', 'list_associations', 'create_association'], + }, + required: true, + }, + { + id: 'propertyName', + title: 'Property Name', + type: 'short-input', + placeholder: 'Leave empty to return all properties (e.g., "hs_lead_status")', + condition: { field: 'operation', value: 'get_properties' }, + }, + { + id: 'archived', + title: 'Archived Only', + type: 'dropdown', + options: [ + { label: 'No', id: 'false' }, + { label: 'Yes', id: 'true' }, + ], + value: () => 'false', + mode: 'advanced', + condition: { field: 'operation', value: 'get_properties' }, + }, + { + id: 'objectId', + title: 'Record ID', + type: 'short-input', + placeholder: 'ID of the source record', + condition: { field: 'operation', value: ['list_associations', 'create_association'] }, + required: true, + }, + { + id: 'toObjectType', + title: 'To Object Type', + type: 'short-input', + placeholder: 'e.g., "emails", "notes", "contacts"', + condition: { field: 'operation', value: ['list_associations', 'create_association'] }, + required: true, + }, + { + id: 'toObjectId', + title: 'To Record ID', + type: 'short-input', + placeholder: 'ID of the target record', + condition: { field: 'operation', value: 'create_association' }, + required: true, + }, + { + id: 'associationCategory', + title: 'Association Category', + type: 'dropdown', + options: [ + { label: 'HubSpot Defined', id: 'HUBSPOT_DEFINED' }, + { label: 'User Defined', id: 'USER_DEFINED' }, + { label: 'Integrator Defined', id: 'INTEGRATOR_DEFINED' }, + ], + value: () => 'HUBSPOT_DEFINED', + mode: 'advanced', + condition: { field: 'operation', value: 'create_association' }, + }, + { + id: 'associationTypeId', + title: 'Association Type ID', + type: 'short-input', + placeholder: 'Leave empty for the default association (e.g., 198 = email→contact)', + mode: 'advanced', + condition: { field: 'operation', value: 'create_association' }, + }, { id: 'lineItemId', title: 'Line Item ID', @@ -266,6 +364,8 @@ export const HubSpotBlock: BlockConfig = { 'update_line_item', 'create_appointment', 'update_appointment', + 'create_note', + 'create_email', ], }, wandConfig: { @@ -403,6 +503,8 @@ Return ONLY the JSON object with properties - no explanations, no markdown, no e 'get_companies', 'get_deals', 'get_tickets', + 'get_notes', + 'get_emails', 'get_line_items', 'get_quotes', 'get_appointments', @@ -423,6 +525,8 @@ Return ONLY the JSON object with properties - no explanations, no markdown, no e 'get_companies', 'get_deals', 'get_tickets', + 'get_notes', + 'get_emails', 'get_line_items', 'get_quotes', 'get_appointments', @@ -433,6 +537,8 @@ Return ONLY the JSON object with properties - no explanations, no markdown, no e 'create_ticket', 'create_line_item', 'create_appointment', + 'create_note', + 'create_email', ], }, }, @@ -450,17 +556,22 @@ Return ONLY the JSON object with properties - no explanations, no markdown, no e 'get_companies', 'get_deals', 'get_tickets', + 'get_notes', + 'get_emails', 'get_line_items', 'get_quotes', 'get_appointments', 'get_carts', 'list_owners', + 'list_associations', 'get_marketing_events', 'get_lists', 'search_contacts', 'search_companies', 'search_deals', 'search_tickets', + 'search_notes', + 'search_emails', ], }, }, @@ -477,11 +588,14 @@ Return ONLY the JSON object with properties - no explanations, no markdown, no e 'get_companies', 'get_deals', 'get_tickets', + 'get_notes', + 'get_emails', 'get_line_items', 'get_quotes', 'get_appointments', 'get_carts', 'list_owners', + 'list_associations', 'get_users', 'get_marketing_events', 'get_lists', @@ -489,6 +603,8 @@ Return ONLY the JSON object with properties - no explanations, no markdown, no e 'search_companies', 'search_deals', 'search_tickets', + 'search_notes', + 'search_emails', ], }, }, @@ -504,6 +620,8 @@ Return ONLY the JSON object with properties - no explanations, no markdown, no e 'search_companies', 'search_deals', 'search_tickets', + 'search_notes', + 'search_emails', 'get_lists', ], }, @@ -516,7 +634,14 @@ Return ONLY the JSON object with properties - no explanations, no markdown, no e 'JSON array of filter groups (e.g., [{"filters":[{"propertyName":"email","operator":"EQ","value":"test@example.com"}]}])', condition: { field: 'operation', - value: ['search_contacts', 'search_companies', 'search_deals', 'search_tickets'], + value: [ + 'search_contacts', + 'search_companies', + 'search_deals', + 'search_tickets', + 'search_notes', + 'search_emails', + ], }, wandConfig: { enabled: true, @@ -720,7 +845,14 @@ Return ONLY the JSON array of filter groups - no explanations, no markdown, no e mode: 'advanced', condition: { field: 'operation', - value: ['search_contacts', 'search_companies', 'search_deals', 'search_tickets'], + value: [ + 'search_contacts', + 'search_companies', + 'search_deals', + 'search_tickets', + 'search_notes', + 'search_emails', + ], }, wandConfig: { enabled: true, @@ -845,7 +977,14 @@ Return ONLY the JSON array of sort objects - no explanations, no markdown, no ex mode: 'advanced', condition: { field: 'operation', - value: ['search_contacts', 'search_companies', 'search_deals', 'search_tickets'], + value: [ + 'search_contacts', + 'search_companies', + 'search_deals', + 'search_tickets', + 'search_notes', + 'search_emails', + ], }, wandConfig: { enabled: true, @@ -998,6 +1137,17 @@ Return ONLY the JSON array of property names - no explanations, no markdown, no 'hubspot_create_ticket', 'hubspot_update_ticket', 'hubspot_search_tickets', + 'hubspot_list_notes', + 'hubspot_get_note', + 'hubspot_create_note', + 'hubspot_search_notes', + 'hubspot_list_emails', + 'hubspot_get_email', + 'hubspot_create_email', + 'hubspot_search_emails', + 'hubspot_get_properties', + 'hubspot_list_associations', + 'hubspot_create_association', 'hubspot_list_line_items', 'hubspot_get_line_item', 'hubspot_create_line_item', @@ -1054,6 +1204,24 @@ Return ONLY the JSON array of property names - no explanations, no markdown, no return 'hubspot_update_ticket' case 'search_tickets': return 'hubspot_search_tickets' + case 'get_notes': + return params.noteId ? 'hubspot_get_note' : 'hubspot_list_notes' + case 'create_note': + return 'hubspot_create_note' + case 'search_notes': + return 'hubspot_search_notes' + case 'get_emails': + return params.emailId ? 'hubspot_get_email' : 'hubspot_list_emails' + case 'create_email': + return 'hubspot_create_email' + case 'search_emails': + return 'hubspot_search_emails' + case 'get_properties': + return 'hubspot_get_properties' + case 'list_associations': + return 'hubspot_list_associations' + case 'create_association': + return 'hubspot_create_association' case 'get_line_items': return params.lineItemId ? 'hubspot_get_line_item' : 'hubspot_list_line_items' case 'create_line_item': @@ -1093,6 +1261,8 @@ Return ONLY the JSON array of property names - no explanations, no markdown, no sorts, associations, listName, + associationTypeId, + archived, ...rest } = params @@ -1113,6 +1283,8 @@ Return ONLY the JSON array of property names - no explanations, no markdown, no 'update_line_item', 'create_appointment', 'update_appointment', + 'create_note', + 'create_email', ] if (propertiesToSet && createUpdateOps.includes(operation as string)) { cleanParams.properties = propertiesToSet @@ -1123,6 +1295,8 @@ Return ONLY the JSON array of property names - no explanations, no markdown, no 'get_companies', 'get_deals', 'get_tickets', + 'get_notes', + 'get_emails', 'get_line_items', 'get_quotes', 'get_appointments', @@ -1132,7 +1306,14 @@ Return ONLY the JSON array of property names - no explanations, no markdown, no cleanParams.properties = properties } - const searchOps = ['search_contacts', 'search_companies', 'search_deals', 'search_tickets'] + const searchOps = [ + 'search_contacts', + 'search_companies', + 'search_deals', + 'search_tickets', + 'search_notes', + 'search_emails', + ] if (searchProperties && searchOps.includes(operation as string)) { cleanParams.properties = searchProperties } @@ -1153,6 +1334,8 @@ Return ONLY the JSON array of property names - no explanations, no markdown, no 'create_ticket', 'create_line_item', 'create_appointment', + 'create_note', + 'create_email', ] if (associations && associationOps.includes(operation as string)) { cleanParams.associations = associations @@ -1162,6 +1345,18 @@ Return ONLY the JSON array of property names - no explanations, no markdown, no cleanParams.name = listName } + if ( + operation === 'create_association' && + associationTypeId !== undefined && + associationTypeId !== '' + ) { + cleanParams.associationTypeId = Number(associationTypeId) + } + + if (operation === 'get_properties' && archived !== undefined && archived !== '') { + cleanParams.archived = archived === true || archived === 'true' + } + if (operation === 'get_lists') { if (rest.limit) { cleanParams.count = rest.limit @@ -1205,6 +1400,16 @@ Return ONLY the JSON array of property names - no explanations, no markdown, no cartId: { type: 'string', description: 'Cart ID' }, eventId: { type: 'string', description: 'Marketing event ID' }, listId: { type: 'string', description: 'List ID' }, + noteId: { type: 'string', description: 'Note ID' }, + emailId: { type: 'string', description: 'Email engagement ID' }, + objectType: { type: 'string', description: 'Object type (e.g., contacts, companies, deals)' }, + propertyName: { type: 'string', description: 'Single property name to retrieve' }, + archived: { type: 'boolean', description: 'Whether to return only archived properties' }, + objectId: { type: 'string', description: 'Source record ID for associations' }, + toObjectType: { type: 'string', description: 'Target object type for associations' }, + toObjectId: { type: 'string', description: 'Target record ID for associations' }, + associationCategory: { type: 'string', description: 'Association category for a labeled link' }, + associationTypeId: { type: 'number', description: 'Association type ID for a labeled link' }, idProperty: { type: 'string', description: 'Property name to use as unique identifier' }, propertiesToSet: { type: 'json', description: 'Properties to create/update (JSON object)' }, properties: { @@ -1245,6 +1450,24 @@ Return ONLY the JSON array of property names - no explanations, no markdown, no event: { type: 'json', description: 'Single marketing event object' }, lists: { type: 'json', description: 'Array of list objects' }, list: { type: 'json', description: 'Single list object' }, + notes: { type: 'json', description: 'Array of note objects' }, + note: { type: 'json', description: 'Single note object' }, + emails: { type: 'json', description: 'Array of email engagement objects' }, + email: { type: 'json', description: 'Single email engagement object' }, + properties: { + type: 'json', + description: 'Array of property definitions (name, label, type, fieldType, options)', + }, + results: { + type: 'json', + description: 'Array of associated records (toObjectId, associationTypes)', + }, + fromObjectId: { type: 'string', description: 'Source record ID (for create association)' }, + toObjectId: { + type: 'string', + description: 'Associated target record ID (for create association)', + }, + labels: { type: 'json', description: 'Association labels (for create association)' }, 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' }, @@ -1331,6 +1554,16 @@ export const HubSpotBlockMeta = { tags: ['support', 'automation', 'crm'], alsoIntegrations: ['slack'], }, + { + icon: HubspotIcon, + title: 'Backfill HubSpot contact email history from Gmail', + prompt: + 'Build a workflow that finds HubSpot contacts in the lead stage with no logged email activity, searches my Gmail for each person’s thread, and logs it back to HubSpot as an email engagement associated with the contact.', + modules: ['agent', 'workflows'], + category: 'sales', + tags: ['sales', 'crm', 'automation'], + alsoIntegrations: ['gmail'], + }, ], skills: [ { @@ -1365,5 +1598,23 @@ export const HubSpotBlockMeta = { content: '# Build Quote From Deal\n\nCompile the commercial details needed to quote a deal.\n\n## Steps\n1. Get the deal by ID for its name, amount, and stage.\n2. List line items and get details to capture product, quantity, and price for each.\n3. Get the associated quote if one exists, or summarize the line items into a draft quote.\n4. Total the line items and compare against the deal amount, flagging mismatches.\n\n## Output\nReturn the deal summary, an itemized line-item list with totals, and any existing quote reference. Flag discrepancies between the line-item total and the deal amount.', }, + { + name: 'log-email-to-contact', + description: 'Log an email engagement in HubSpot and associate it with a contact.', + content: + '# Log Email To Contact\n\nRecord an email activity on a contact’s timeline.\n\n## Steps\n1. Search contacts by email to resolve the contact ID.\n2. Create an email engagement with hs_timestamp, subject, body, and direction.\n3. Associate the email with the contact (associationTypeId 198, or the default association).\n4. List associations from the contact to emails to confirm the link.\n\n## Output\nReturn the email engagement ID and the associated contact ID.', + }, + { + name: 'audit-contacts-missing-activity', + description: 'Find contacts in a lead stage that have no logged email activity.', + content: + '# Audit Contacts Missing Activity\n\nSurface leads with no recorded email history.\n\n## Steps\n1. Get properties for contacts to read the hs_lead_status options and confirm the target stage value.\n2. Search contacts filtered to that lead status, paginating through all results.\n3. For each contact, list associations to emails and flag those with zero associated emails.\n4. Collect the contacts that need follow-up.\n\n## Output\nReturn the list of contact IDs with no logged email activity, ready for backfill.', + }, + { + name: 'inspect-property-options', + description: 'Read the enumeration (picklist) values for a HubSpot property.', + content: + '# Inspect Property Options\n\nList the allowed values for a dropdown property.\n\n## Steps\n1. Get properties for the object type (e.g., contacts).\n2. Find the property by name (e.g., lifecyclestage or hs_lead_status).\n3. Read its options array for label/value pairs.\n\n## Output\nReturn the property label and its enumeration options as label/value pairs.', + }, ], } as const satisfies BlockMeta diff --git a/apps/sim/lib/integrations/integrations.json b/apps/sim/lib/integrations/integrations.json index c9e426a8aaf..0c3a3b6fcb6 100644 --- a/apps/sim/lib/integrations/integrations.json +++ b/apps/sim/lib/integrations/integrations.json @@ -1,5 +1,5 @@ { - "updatedAt": "2026-06-13", + "updatedAt": "2026-06-14", "integrations": [ { "type": "onepassword", @@ -7191,7 +7191,7 @@ }, { "name": "Create Deal", - "description": "Create a new deal in HubSpot. Requires at least a dealname property" + "description": "Create a new deal in HubSpot with the given properties (e.g., dealname, amount, dealstage)" }, { "name": "Update Deal", @@ -7217,6 +7217,42 @@ "name": "Search Tickets", "description": "Search for tickets in HubSpot using filters, sorting, and queries" }, + { + "name": "Get Notes", + "description": "Retrieve all notes from HubSpot account with pagination support" + }, + { + "name": "Create Note", + "description": "Log a note in HubSpot and optionally associate it with contacts, companies, or deals. Requires hs_timestamp and hs_note_body properties" + }, + { + "name": "Search Notes", + "description": "Search for notes in HubSpot using filters, sorting, and queries" + }, + { + "name": "Get Emails", + "description": "Retrieve all email engagements from HubSpot account with pagination support" + }, + { + "name": "Create Email", + "description": "Log an email engagement in HubSpot and optionally associate it with contacts. Requires the hs_timestamp property" + }, + { + "name": "Search Emails", + "description": "Search for email engagements in HubSpot using filters, sorting, and queries" + }, + { + "name": "Get Properties", + "description": "Read property definitions and their enumeration (picklist) options for a HubSpot object type, e.g. the values for lifecyclestage or hs_lead_status on contacts" + }, + { + "name": "List Associations", + "description": "List records of one object type associated with a given record, e.g. all emails or notes logged on a contact" + }, + { + "name": "Create Association", + "description": "Associate two HubSpot records. Creates the default (unlabeled) association unless an association type ID is provided" + }, { "name": "Get Line Items", "description": "Retrieve all line items from HubSpot account with pagination support" @@ -7270,7 +7306,7 @@ "description": "Retrieve all users from HubSpot account" } ], - "operationCount": 29, + "operationCount": 38, "triggers": [ { "id": "hubspot_poller", diff --git a/apps/sim/lib/oauth/oauth.ts b/apps/sim/lib/oauth/oauth.ts index 28c30e0c238..8c1594aa5b9 100644 --- a/apps/sim/lib/oauth/oauth.ts +++ b/apps/sim/lib/oauth/oauth.ts @@ -917,6 +917,11 @@ export const OAUTH_PROVIDERS: Record = { 'crm.objects.appointments.read', 'crm.objects.appointments.write', 'crm.objects.carts.read', + 'crm.objects.notes.read', + 'crm.objects.notes.write', + 'crm.objects.emails.read', + 'crm.objects.emails.write', + 'sales-email-read', 'crm.lists.read', 'crm.lists.write', 'tickets', diff --git a/apps/sim/lib/oauth/utils.ts b/apps/sim/lib/oauth/utils.ts index fbb475cfe51..cf92ae13f42 100644 --- a/apps/sim/lib/oauth/utils.ts +++ b/apps/sim/lib/oauth/utils.ts @@ -314,6 +314,11 @@ export const SCOPE_DESCRIPTIONS: Record = { 'crm.objects.appointments.write': 'Create and update HubSpot appointments', 'crm.objects.carts.read': 'Read HubSpot shopping carts', 'crm.objects.carts.write': 'Create and update HubSpot shopping carts', + 'crm.objects.notes.read': 'Read HubSpot notes', + 'crm.objects.notes.write': 'Create and update HubSpot notes', + 'crm.objects.emails.read': 'Read HubSpot email engagements', + 'crm.objects.emails.write': 'Create and update HubSpot email engagements', + 'sales-email-read': 'Read the content of HubSpot email engagements', 'crm.import': 'Import data into HubSpot', 'crm.lists.read': 'Read HubSpot lists', 'crm.lists.write': 'Create and update HubSpot lists', diff --git a/apps/sim/tools/hubspot/create_appointment.ts b/apps/sim/tools/hubspot/create_appointment.ts index c3d742ee028..89c96c02d45 100644 --- a/apps/sim/tools/hubspot/create_appointment.ts +++ b/apps/sim/tools/hubspot/create_appointment.ts @@ -34,7 +34,7 @@ export const hubspotCreateAppointmentTool: ToolConfig< 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"})', + '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"})', }, associations: { type: 'array', @@ -67,8 +67,18 @@ export const hubspotCreateAppointmentTool: ToolConfig< } } const body: Record = { properties } - if (params.associations && params.associations.length > 0) { - body.associations = params.associations + let associations = params.associations + if (typeof associations === 'string') { + try { + associations = JSON.parse(associations) + } catch (e) { + throw new Error( + 'Invalid JSON format for associations. Please provide a valid JSON array.' + ) + } + } + if (Array.isArray(associations) && associations.length > 0) { + body.associations = associations } return body }, diff --git a/apps/sim/tools/hubspot/create_association.ts b/apps/sim/tools/hubspot/create_association.ts new file mode 100644 index 00000000000..2a531b6b88d --- /dev/null +++ b/apps/sim/tools/hubspot/create_association.ts @@ -0,0 +1,141 @@ +import { createLogger } from '@sim/logger' +import type { + HubSpotCreateAssociationParams, + HubSpotCreateAssociationResponse, +} from '@/tools/hubspot/types' +import type { ToolConfig } from '@/tools/types' + +const logger = createLogger('HubSpotCreateAssociation') + +export const hubspotCreateAssociationTool: ToolConfig< + HubSpotCreateAssociationParams, + HubSpotCreateAssociationResponse +> = { + id: 'hubspot_create_association', + name: 'Create Association in HubSpot', + description: + 'Associate two HubSpot records. Creates the default (unlabeled) association unless an association type ID is provided', + 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', + }, + objectType: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Source object type (e.g., "emails", "notes", "contacts")', + }, + objectId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'ID of the source record', + }, + toObjectType: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Target object type to associate to (e.g., "contacts", "companies", "deals")', + }, + toObjectId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'ID of the target record', + }, + associationCategory: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Association category for a labeled association (HUBSPOT_DEFINED, USER_DEFINED, INTEGRATOR_DEFINED). Defaults to HUBSPOT_DEFINED when an association type ID is provided', + }, + associationTypeId: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: + 'Specific association type ID for a labeled association. Omit to create the default association for the object pair', + }, + }, + + request: { + url: (params) => { + const from = `${encodeURIComponent(params.objectType.trim())}/${encodeURIComponent(params.objectId.trim())}` + const to = `${encodeURIComponent(params.toObjectType.trim())}/${encodeURIComponent(params.toObjectId.trim())}` + + // A specific type ID means a labeled association; otherwise create the default. + return params.associationTypeId != null + ? `https://api.hubapi.com/crm/v4/objects/${from}/associations/${to}` + : `https://api.hubapi.com/crm/v4/objects/${from}/associations/default/${to}` + }, + method: 'PUT', + headers: (params) => { + if (!params.accessToken) { + throw new Error('Access token is required') + } + + return { + Authorization: `Bearer ${params.accessToken}`, + 'Content-Type': 'application/json', + } + }, + body: (params) => { + // The default-association endpoint takes no body. + if (params.associationTypeId == null) { + return undefined + } + + return [ + { + associationCategory: params.associationCategory || 'HUBSPOT_DEFINED', + associationTypeId: params.associationTypeId, + }, + ] + }, + }, + + transformResponse: async (response: Response, params) => { + 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 association in HubSpot') + } + + // Labeled associations return LabelsBetweenObjectPair; default associations + // return a batch response with a single result. + const batchResult = Array.isArray(data.results) ? data.results[0] : undefined + + return { + success: true, + output: { + fromObjectId: data.fromObjectId ?? batchResult?.from?.id ?? params?.objectId ?? '', + toObjectId: data.toObjectId ?? batchResult?.to?.id ?? params?.toObjectId ?? '', + labels: data.labels ?? [], + success: true, + }, + } + }, + + outputs: { + fromObjectId: { type: 'string', description: 'ID of the source record' }, + toObjectId: { type: 'string', description: 'ID of the associated target record' }, + labels: { + type: 'array', + description: 'Association labels (empty for default associations)', + items: { type: 'string', description: 'Association label' }, + }, + success: { type: 'boolean', description: 'Operation success status' }, + }, +} diff --git a/apps/sim/tools/hubspot/create_company.ts b/apps/sim/tools/hubspot/create_company.ts index 05afaf00f99..9e78c180eeb 100644 --- a/apps/sim/tools/hubspot/create_company.ts +++ b/apps/sim/tools/hubspot/create_company.ts @@ -72,8 +72,18 @@ export const hubspotCreateCompanyTool: ToolConfig< properties, } - if (params.associations && params.associations.length > 0) { - body.associations = params.associations + let associations = params.associations + if (typeof associations === 'string') { + try { + associations = JSON.parse(associations) + } catch (e) { + throw new Error( + 'Invalid JSON format for associations. Please provide a valid JSON array.' + ) + } + } + if (Array.isArray(associations) && associations.length > 0) { + body.associations = associations } return body diff --git a/apps/sim/tools/hubspot/create_contact.ts b/apps/sim/tools/hubspot/create_contact.ts index 17af77096bf..dd0360f25b6 100644 --- a/apps/sim/tools/hubspot/create_contact.ts +++ b/apps/sim/tools/hubspot/create_contact.ts @@ -73,8 +73,18 @@ export const hubspotCreateContactTool: ToolConfig< properties, } - if (params.associations && params.associations.length > 0) { - body.associations = params.associations + let associations = params.associations + if (typeof associations === 'string') { + try { + associations = JSON.parse(associations) + } catch (e) { + throw new Error( + 'Invalid JSON format for associations. Please provide a valid JSON array.' + ) + } + } + if (Array.isArray(associations) && associations.length > 0) { + body.associations = associations } return body diff --git a/apps/sim/tools/hubspot/create_deal.ts b/apps/sim/tools/hubspot/create_deal.ts index 5781a383362..6b5a60ef336 100644 --- a/apps/sim/tools/hubspot/create_deal.ts +++ b/apps/sim/tools/hubspot/create_deal.ts @@ -9,7 +9,8 @@ export const hubspotCreateDealTool: ToolConfig = { properties } - if (params.associations && params.associations.length > 0) { - body.associations = params.associations + let associations = params.associations + if (typeof associations === 'string') { + try { + associations = JSON.parse(associations) + } catch (e) { + throw new Error( + 'Invalid JSON format for associations. Please provide a valid JSON array.' + ) + } + } + if (Array.isArray(associations) && associations.length > 0) { + body.associations = associations } return body }, diff --git a/apps/sim/tools/hubspot/create_email.ts b/apps/sim/tools/hubspot/create_email.ts new file mode 100644 index 00000000000..c1f508183cb --- /dev/null +++ b/apps/sim/tools/hubspot/create_email.ts @@ -0,0 +1,114 @@ +import { createLogger } from '@sim/logger' +import type { HubSpotCreateEmailParams, HubSpotCreateEmailResponse } from '@/tools/hubspot/types' +import { EMAIL_OBJECT_OUTPUT } from '@/tools/hubspot/types' +import type { ToolConfig } from '@/tools/types' + +const logger = createLogger('HubSpotCreateEmail') + +export const hubspotCreateEmailTool: ToolConfig< + HubSpotCreateEmailParams, + HubSpotCreateEmailResponse +> = { + id: 'hubspot_create_email', + name: 'Create Email in HubSpot', + description: + 'Log an email engagement in HubSpot and optionally associate it with contacts. Requires the hs_timestamp 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: + 'Email properties as JSON object. Must include "hs_timestamp" (ISO 8601). Common fields: "hs_email_direction" (EMAIL, INCOMING_EMAIL, FORWARDED_EMAIL), "hs_email_status" (SENT, SENDING, SCHEDULED, FAILED, BOUNCED), "hs_email_subject", "hs_email_text", "hs_email_html", "hs_email_headers" (JSON string)', + }, + associations: { + type: 'array', + required: false, + visibility: 'user-or-llm', + description: + 'Array of associations as JSON. Each object has "to.id" (record ID) and "types" array with "associationCategory" ("HUBSPOT_DEFINED") and "associationTypeId" (198 = email→contact)', + }, + }, + + request: { + url: () => 'https://api.hubapi.com/crm/v3/objects/emails', + 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: any = { + properties, + } + + let associations = params.associations + if (typeof associations === 'string') { + try { + associations = JSON.parse(associations) + } catch (e) { + throw new Error( + 'Invalid JSON format for associations. Please provide a valid JSON array.' + ) + } + } + if (Array.isArray(associations) && associations.length > 0) { + body.associations = 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 email in HubSpot') + } + + return { + success: true, + output: { + email: data, + emailId: data.id, + success: true, + }, + } + }, + + outputs: { + email: EMAIL_OBJECT_OUTPUT, + emailId: { type: 'string', description: 'The created email engagement 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 index ebf26c412a1..f44b6c1ec1d 100644 --- a/apps/sim/tools/hubspot/create_line_item.ts +++ b/apps/sim/tools/hubspot/create_line_item.ts @@ -67,8 +67,18 @@ export const hubspotCreateLineItemTool: ToolConfig< } } const body: Record = { properties } - if (params.associations && params.associations.length > 0) { - body.associations = params.associations + let associations = params.associations + if (typeof associations === 'string') { + try { + associations = JSON.parse(associations) + } catch (e) { + throw new Error( + 'Invalid JSON format for associations. Please provide a valid JSON array.' + ) + } + } + if (Array.isArray(associations) && associations.length > 0) { + body.associations = associations } return body }, diff --git a/apps/sim/tools/hubspot/create_list.ts b/apps/sim/tools/hubspot/create_list.ts index 40bc6a68d2a..fa1c5ea78ed 100644 --- a/apps/sim/tools/hubspot/create_list.ts +++ b/apps/sim/tools/hubspot/create_list.ts @@ -72,7 +72,11 @@ export const hubspotCreateListTool: ToolConfig = + { + id: 'hubspot_create_note', + name: 'Create Note in HubSpot', + description: + 'Log a note in HubSpot and optionally associate it with contacts, companies, or deals. Requires hs_timestamp and hs_note_body 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: + 'Note properties as JSON object. Must include "hs_timestamp" (ISO 8601 activity time) and "hs_note_body" (the note text). e.g., {"hs_timestamp": "2026-06-13T00:00:00Z", "hs_note_body": "Followed up via phone"}', + }, + associations: { + type: 'array', + required: false, + visibility: 'user-or-llm', + description: + 'Array of associations as JSON. Each object has "to.id" (record ID) and "types" array with "associationCategory" ("HUBSPOT_DEFINED") and "associationTypeId" (202 = note→contact, 190 = note→company, 214 = note→deal)', + }, + }, + + request: { + url: () => 'https://api.hubapi.com/crm/v3/objects/notes', + 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: any = { + properties, + } + + let associations = params.associations + if (typeof associations === 'string') { + try { + associations = JSON.parse(associations) + } catch (e) { + throw new Error( + 'Invalid JSON format for associations. Please provide a valid JSON array.' + ) + } + } + if (Array.isArray(associations) && associations.length > 0) { + body.associations = 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 note in HubSpot') + } + + return { + success: true, + output: { + note: data, + noteId: data.id, + success: true, + }, + } + }, + + outputs: { + note: NOTE_OBJECT_OUTPUT, + noteId: { type: 'string', description: 'The created note 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 index b6ebaa1fa3f..c5b20b597f0 100644 --- a/apps/sim/tools/hubspot/create_ticket.ts +++ b/apps/sim/tools/hubspot/create_ticket.ts @@ -64,8 +64,18 @@ export const hubspotCreateTicketTool: ToolConfig< } } const body: Record = { properties } - if (params.associations && params.associations.length > 0) { - body.associations = params.associations + let associations = params.associations + if (typeof associations === 'string') { + try { + associations = JSON.parse(associations) + } catch (e) { + throw new Error( + 'Invalid JSON format for associations. Please provide a valid JSON array.' + ) + } + } + if (Array.isArray(associations) && associations.length > 0) { + body.associations = associations } return body }, diff --git a/apps/sim/tools/hubspot/get_appointment.ts b/apps/sim/tools/hubspot/get_appointment.ts index 5dceb1953d1..c0c3a59217e 100644 --- a/apps/sim/tools/hubspot/get_appointment.ts +++ b/apps/sim/tools/hubspot/get_appointment.ts @@ -46,7 +46,7 @@ export const hubspotGetAppointmentTool: ToolConfig< 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")', + 'Comma-separated list of HubSpot property names to return (e.g., "hs_appointment_name,hs_appointment_start")', }, associations: { type: 'string', diff --git a/apps/sim/tools/hubspot/get_email.ts b/apps/sim/tools/hubspot/get_email.ts new file mode 100644 index 00000000000..0097867bd1c --- /dev/null +++ b/apps/sim/tools/hubspot/get_email.ts @@ -0,0 +1,100 @@ +import { createLogger } from '@sim/logger' +import type { HubSpotGetEmailParams, HubSpotGetEmailResponse } from '@/tools/hubspot/types' +import { EMAIL_OBJECT_OUTPUT } from '@/tools/hubspot/types' +import type { ToolConfig } from '@/tools/types' + +const logger = createLogger('HubSpotGetEmail') + +export const hubspotGetEmailTool: ToolConfig = { + id: 'hubspot_get_email', + name: 'Get Email from HubSpot', + description: + 'Retrieve a single email engagement by ID from HubSpot (content requires the sales-email-read scope)', + 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', + }, + emailId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The HubSpot email engagement ID to retrieve', + }, + properties: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Comma-separated list of HubSpot property names to return (e.g., "hs_email_subject,hs_email_text,hs_timestamp")', + }, + 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,deals")', + }, + }, + + request: { + url: (params) => { + const baseUrl = `https://api.hubapi.com/crm/v3/objects/emails/${params.emailId.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 email from HubSpot') + } + + return { + success: true, + output: { + email: data, + emailId: data.id, + success: true, + }, + } + }, + + outputs: { + email: EMAIL_OBJECT_OUTPUT, + emailId: { type: 'string', description: 'The retrieved email engagement ID' }, + success: { type: 'boolean', description: 'Operation success status' }, + }, +} diff --git a/apps/sim/tools/hubspot/get_note.ts b/apps/sim/tools/hubspot/get_note.ts new file mode 100644 index 00000000000..4e41f0ae00e --- /dev/null +++ b/apps/sim/tools/hubspot/get_note.ts @@ -0,0 +1,99 @@ +import { createLogger } from '@sim/logger' +import type { HubSpotGetNoteParams, HubSpotGetNoteResponse } from '@/tools/hubspot/types' +import { NOTE_OBJECT_OUTPUT } from '@/tools/hubspot/types' +import type { ToolConfig } from '@/tools/types' + +const logger = createLogger('HubSpotGetNote') + +export const hubspotGetNoteTool: ToolConfig = { + id: 'hubspot_get_note', + name: 'Get Note from HubSpot', + description: 'Retrieve a single note 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', + }, + noteId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The HubSpot note ID to retrieve', + }, + properties: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Comma-separated list of HubSpot property names to return (e.g., "hs_note_body,hs_timestamp,hubspot_owner_id")', + }, + 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,deals")', + }, + }, + + request: { + url: (params) => { + const baseUrl = `https://api.hubapi.com/crm/v3/objects/notes/${params.noteId.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 note from HubSpot') + } + + return { + success: true, + output: { + note: data, + noteId: data.id, + success: true, + }, + } + }, + + outputs: { + note: NOTE_OBJECT_OUTPUT, + noteId: { type: 'string', description: 'The retrieved note ID' }, + success: { type: 'boolean', description: 'Operation success status' }, + }, +} diff --git a/apps/sim/tools/hubspot/get_properties.ts b/apps/sim/tools/hubspot/get_properties.ts new file mode 100644 index 00000000000..527fd1555e5 --- /dev/null +++ b/apps/sim/tools/hubspot/get_properties.ts @@ -0,0 +1,120 @@ +import { createLogger } from '@sim/logger' +import type { + HubSpotGetPropertiesParams, + HubSpotGetPropertiesResponse, +} from '@/tools/hubspot/types' +import { PROPERTIES_ARRAY_OUTPUT } from '@/tools/hubspot/types' +import type { ToolConfig } from '@/tools/types' + +const logger = createLogger('HubSpotGetProperties') + +export const hubspotGetPropertiesTool: ToolConfig< + HubSpotGetPropertiesParams, + HubSpotGetPropertiesResponse +> = { + id: 'hubspot_get_properties', + name: 'Get Properties from HubSpot', + description: + 'Read property definitions and their enumeration (picklist) options for a HubSpot object type, e.g. the values for lifecyclestage or hs_lead_status on contacts', + 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', + }, + objectType: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: + 'Object type to read properties for (e.g., "contacts", "companies", "deals", "tickets", "line_items", "quotes")', + }, + propertyName: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Internal name of a single property to retrieve (e.g., "hs_lead_status"). Omit to return all properties for the object type', + }, + archived: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Whether to return only archived properties (default false)', + }, + }, + + request: { + url: (params) => { + const objectType = params.objectType.trim() + const baseUrl = params.propertyName + ? `https://api.hubapi.com/crm/v3/properties/${encodeURIComponent(objectType)}/${encodeURIComponent(params.propertyName.trim())}` + : `https://api.hubapi.com/crm/v3/properties/${encodeURIComponent(objectType)}` + + const queryParams = new URLSearchParams() + if (params.archived !== undefined) { + queryParams.append('archived', String(params.archived)) + } + + 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, params) => { + 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 properties from HubSpot') + } + + // The list endpoint returns { results: Property[] }; the single-property + // endpoint returns the Property object directly. + const properties = Array.isArray(data.results) ? data.results : [data] + + return { + success: true, + output: { + properties, + metadata: { + totalReturned: properties.length, + objectType: params?.objectType ?? '', + }, + success: true, + }, + } + }, + + outputs: { + properties: PROPERTIES_ARRAY_OUTPUT, + metadata: { + type: 'object', + description: 'Response metadata', + properties: { + totalReturned: { type: 'number', description: 'Number of property definitions returned' }, + objectType: { type: 'string', description: 'Object type the properties belong to' }, + }, + }, + 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 5a23a6d2c41..5d875944eb7 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 { PAGING_OUTPUT, USERS_ARRAY_OUTPUT } from '@/tools/hubspot/types' +import { GENERIC_CRM_ARRAY_OUTPUT, PAGING_OUTPUT } from '@/tools/hubspot/types' import type { ToolConfig } from '@/tools/types' const logger = createLogger('HubSpotGetUsers') @@ -27,7 +27,7 @@ export const hubspotGetUsersTool: ToolConfig = { + id: 'hubspot_list_associations', + name: 'List Associations in HubSpot', + description: + 'List records of one object type associated with a given record, e.g. all emails or notes logged on a contact', + 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', + }, + objectType: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Source object type (e.g., "contacts", "companies", "deals")', + }, + objectId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'ID of the source record', + }, + toObjectType: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Target object type to list associations to (e.g., "emails", "notes", "deals")', + }, + limit: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of associated records per page (default 500)', + }, + after: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Pagination cursor for next page (from previous response)', + }, + }, + + request: { + url: (params) => { + const baseUrl = `https://api.hubapi.com/crm/v4/objects/${encodeURIComponent(params.objectType.trim())}/${encodeURIComponent(params.objectId.trim())}/associations/${encodeURIComponent(params.toObjectType.trim())}` + 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 associations from HubSpot') + } + + return { + success: true, + output: { + results: data.results || [], + paging: data.paging ?? null, + metadata: { + totalReturned: data.results?.length || 0, + hasMore: !!data.paging?.next, + }, + success: true, + }, + } + }, + + outputs: { + results: ASSOCIATIONS_ARRAY_OUTPUT, + paging: PAGING_OUTPUT, + metadata: METADATA_OUTPUT, + success: { type: 'boolean', description: 'Operation success status' }, + }, +} diff --git a/apps/sim/tools/hubspot/list_companies.ts b/apps/sim/tools/hubspot/list_companies.ts index 218d65e7547..a2cbbf96292 100644 --- a/apps/sim/tools/hubspot/list_companies.ts +++ b/apps/sim/tools/hubspot/list_companies.ts @@ -33,7 +33,7 @@ export const hubspotListCompaniesTool: ToolConfig< type: 'string', required: false, visibility: 'user-or-llm', - description: 'Maximum number of results per page (max 100, default 100)', + description: 'Maximum number of results per page (max 100, default 10)', }, after: { type: 'string', diff --git a/apps/sim/tools/hubspot/list_contacts.ts b/apps/sim/tools/hubspot/list_contacts.ts index 3ec291fdab0..5aa0c5c029b 100644 --- a/apps/sim/tools/hubspot/list_contacts.ts +++ b/apps/sim/tools/hubspot/list_contacts.ts @@ -30,7 +30,7 @@ export const hubspotListContactsTool: ToolConfig< type: 'string', required: false, visibility: 'user-or-llm', - description: 'Maximum number of results per page (max 100, default 100)', + description: 'Maximum number of results per page (max 100, default 10)', }, after: { type: 'string', diff --git a/apps/sim/tools/hubspot/list_emails.ts b/apps/sim/tools/hubspot/list_emails.ts new file mode 100644 index 00000000000..47dbac7601b --- /dev/null +++ b/apps/sim/tools/hubspot/list_emails.ts @@ -0,0 +1,117 @@ +import { createLogger } from '@sim/logger' +import type { HubSpotListEmailsParams, HubSpotListEmailsResponse } from '@/tools/hubspot/types' +import { EMAILS_ARRAY_OUTPUT, METADATA_OUTPUT, PAGING_OUTPUT } from '@/tools/hubspot/types' +import type { ToolConfig } from '@/tools/types' + +const logger = createLogger('HubSpotListEmails') + +export const hubspotListEmailsTool: ToolConfig = + { + id: 'hubspot_list_emails', + name: 'List Emails from HubSpot', + description: 'Retrieve all email engagements 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_email_subject,hs_email_text,hs_timestamp")', + }, + 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,deals")', + }, + }, + + request: { + url: (params) => { + const baseUrl = 'https://api.hubapi.com/crm/v3/objects/emails' + 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 emails from HubSpot') + } + + return { + success: true, + output: { + emails: data.results || [], + paging: data.paging ?? null, + metadata: { + totalReturned: data.results?.length || 0, + hasMore: !!data.paging?.next, + }, + success: true, + }, + } + }, + + outputs: { + emails: EMAILS_ARRAY_OUTPUT, + paging: PAGING_OUTPUT, + metadata: METADATA_OUTPUT, + 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 index 2d77bec60ec..29a75333d99 100644 --- a/apps/sim/tools/hubspot/list_marketing_events.ts +++ b/apps/sim/tools/hubspot/list_marketing_events.ts @@ -49,7 +49,7 @@ export const hubspotListMarketingEventsTool: ToolConfig< request: { url: (params) => { - const baseUrl = 'https://api.hubapi.com/marketing/v3/marketing-events' + const baseUrl = 'https://api.hubapi.com/marketing/marketing-events/v3' const queryParams = new URLSearchParams() if (params.limit) queryParams.append('limit', params.limit) if (params.after) queryParams.append('after', params.after) diff --git a/apps/sim/tools/hubspot/list_notes.ts b/apps/sim/tools/hubspot/list_notes.ts new file mode 100644 index 00000000000..a6b7191c04a --- /dev/null +++ b/apps/sim/tools/hubspot/list_notes.ts @@ -0,0 +1,116 @@ +import { createLogger } from '@sim/logger' +import type { HubSpotListNotesParams, HubSpotListNotesResponse } from '@/tools/hubspot/types' +import { METADATA_OUTPUT, NOTES_ARRAY_OUTPUT, PAGING_OUTPUT } from '@/tools/hubspot/types' +import type { ToolConfig } from '@/tools/types' + +const logger = createLogger('HubSpotListNotes') + +export const hubspotListNotesTool: ToolConfig = { + id: 'hubspot_list_notes', + name: 'List Notes from HubSpot', + description: 'Retrieve all notes 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_note_body,hs_timestamp,hubspot_owner_id")', + }, + 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,deals")', + }, + }, + + request: { + url: (params) => { + const baseUrl = 'https://api.hubapi.com/crm/v3/objects/notes' + 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 notes from HubSpot') + } + + return { + success: true, + output: { + notes: data.results || [], + paging: data.paging ?? null, + metadata: { + totalReturned: data.results?.length || 0, + hasMore: !!data.paging?.next, + }, + success: true, + }, + } + }, + + outputs: { + notes: NOTES_ARRAY_OUTPUT, + paging: PAGING_OUTPUT, + metadata: METADATA_OUTPUT, + success: { type: 'boolean', description: 'Operation success status' }, + }, +} diff --git a/apps/sim/tools/hubspot/search_emails.ts b/apps/sim/tools/hubspot/search_emails.ts new file mode 100644 index 00000000000..600f47c6dd8 --- /dev/null +++ b/apps/sim/tools/hubspot/search_emails.ts @@ -0,0 +1,169 @@ +import { createLogger } from '@sim/logger' +import type { HubSpotSearchEmailsParams, HubSpotSearchEmailsResponse } from '@/tools/hubspot/types' +import { EMAILS_ARRAY_OUTPUT, METADATA_OUTPUT, PAGING_OUTPUT } from '@/tools/hubspot/types' +import type { ToolConfig } from '@/tools/types' + +const logger = createLogger('HubSpotSearchEmails') + +export const hubspotSearchEmailsTool: ToolConfig< + HubSpotSearchEmailsParams, + HubSpotSearchEmailsResponse +> = { + id: 'hubspot_search_emails', + name: 'Search Emails in HubSpot', + description: 'Search for email engagements 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", "CONTAINS_TOKEN", "GT"), 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 email text fields', + }, + properties: { + type: 'array', + required: false, + visibility: 'user-or-llm', + description: + 'Array of HubSpot property names to return (e.g., ["hs_email_subject", "hs_email_text", "hs_timestamp"])', + }, + limit: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of results to return (max 100)', + }, + 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/emails/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: any = {} + + if (params.filterGroups) { + let parsedFilterGroups = params.filterGroups + if (typeof params.filterGroups === 'string') { + try { + parsedFilterGroups = JSON.parse(params.filterGroups) + } catch (e) { + throw new Error(`Invalid JSON for filterGroups: ${(e as Error).message}`) + } + } + if (Array.isArray(parsedFilterGroups) && parsedFilterGroups.length > 0) { + body.filterGroups = parsedFilterGroups + } + } + if (params.sorts) { + let parsedSorts = params.sorts + if (typeof params.sorts === 'string') { + try { + parsedSorts = JSON.parse(params.sorts) + } catch (e) { + throw new Error(`Invalid JSON for sorts: ${(e as Error).message}`) + } + } + if (Array.isArray(parsedSorts) && parsedSorts.length > 0) { + body.sorts = parsedSorts + } + } + if (params.query) { + body.query = params.query + } + if (params.properties) { + let parsedProperties = params.properties + if (typeof params.properties === 'string') { + try { + parsedProperties = JSON.parse(params.properties) + } catch (e) { + throw new Error(`Invalid JSON for properties: ${(e as Error).message}`) + } + } + if (Array.isArray(parsedProperties) && parsedProperties.length > 0) { + body.properties = parsedProperties + } + } + 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 emails in HubSpot') + } + + return { + success: true, + output: { + emails: data.results || [], + total: data.total ?? 0, + paging: data.paging ?? null, + metadata: { + totalReturned: data.results?.length || 0, + hasMore: !!data.paging?.next, + }, + success: true, + }, + } + }, + + outputs: { + emails: EMAILS_ARRAY_OUTPUT, + total: { type: 'number', description: 'Total number of matching emails', optional: true }, + paging: PAGING_OUTPUT, + metadata: METADATA_OUTPUT, + success: { type: 'boolean', description: 'Operation success status' }, + }, +} diff --git a/apps/sim/tools/hubspot/search_notes.ts b/apps/sim/tools/hubspot/search_notes.ts new file mode 100644 index 00000000000..bed86d666e0 --- /dev/null +++ b/apps/sim/tools/hubspot/search_notes.ts @@ -0,0 +1,169 @@ +import { createLogger } from '@sim/logger' +import type { HubSpotSearchNotesParams, HubSpotSearchNotesResponse } from '@/tools/hubspot/types' +import { METADATA_OUTPUT, NOTES_ARRAY_OUTPUT, PAGING_OUTPUT } from '@/tools/hubspot/types' +import type { ToolConfig } from '@/tools/types' + +const logger = createLogger('HubSpotSearchNotes') + +export const hubspotSearchNotesTool: ToolConfig< + HubSpotSearchNotesParams, + HubSpotSearchNotesResponse +> = { + id: 'hubspot_search_notes', + name: 'Search Notes in HubSpot', + description: 'Search for notes 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", "CONTAINS_TOKEN", "GT"), 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 note text fields', + }, + properties: { + type: 'array', + required: false, + visibility: 'user-or-llm', + description: + 'Array of HubSpot property names to return (e.g., ["hs_note_body", "hs_timestamp", "hubspot_owner_id"])', + }, + limit: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of results to return (max 100)', + }, + 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/notes/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: any = {} + + if (params.filterGroups) { + let parsedFilterGroups = params.filterGroups + if (typeof params.filterGroups === 'string') { + try { + parsedFilterGroups = JSON.parse(params.filterGroups) + } catch (e) { + throw new Error(`Invalid JSON for filterGroups: ${(e as Error).message}`) + } + } + if (Array.isArray(parsedFilterGroups) && parsedFilterGroups.length > 0) { + body.filterGroups = parsedFilterGroups + } + } + if (params.sorts) { + let parsedSorts = params.sorts + if (typeof params.sorts === 'string') { + try { + parsedSorts = JSON.parse(params.sorts) + } catch (e) { + throw new Error(`Invalid JSON for sorts: ${(e as Error).message}`) + } + } + if (Array.isArray(parsedSorts) && parsedSorts.length > 0) { + body.sorts = parsedSorts + } + } + if (params.query) { + body.query = params.query + } + if (params.properties) { + let parsedProperties = params.properties + if (typeof params.properties === 'string') { + try { + parsedProperties = JSON.parse(params.properties) + } catch (e) { + throw new Error(`Invalid JSON for properties: ${(e as Error).message}`) + } + } + if (Array.isArray(parsedProperties) && parsedProperties.length > 0) { + body.properties = parsedProperties + } + } + 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 notes in HubSpot') + } + + return { + success: true, + output: { + notes: data.results || [], + total: data.total ?? 0, + paging: data.paging ?? null, + metadata: { + totalReturned: data.results?.length || 0, + hasMore: !!data.paging?.next, + }, + success: true, + }, + } + }, + + outputs: { + notes: NOTES_ARRAY_OUTPUT, + total: { type: 'number', description: 'Total number of matching notes', 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 e89efac1689..448ac87bf33 100644 --- a/apps/sim/tools/hubspot/types.ts +++ b/apps/sim/tools/hubspot/types.ts @@ -351,11 +351,10 @@ export const QUOTE_PROPERTIES_OUTPUT = { * @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' }, + hs_appointment_name: { type: 'string', description: 'Appointment title/name' }, + hs_appointment_start: { type: 'string', description: 'Start time (ISO 8601)' }, + hs_appointment_end: { type: 'string', description: 'End time (ISO 8601)' }, + hs_appointment_status: { type: 'string', description: 'Appointment status (e.g., SCHEDULED)' }, 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)' }, @@ -680,48 +679,218 @@ export const LISTS_ARRAY_OUTPUT: OutputProperty = { } /** - * User properties returned by HubSpot Settings API v3. - * Note: firstName and lastName are NOT returned by the Settings API. - * Use the Owners API if you need user names. - * @see https://developers.hubspot.com/docs/reference/api/settings/users/user-provisioning +/** + * Common note properties returned by HubSpot API. + * @see https://developers.hubspot.com/docs/api-reference/crm-notes-v3 */ -export const USER_OUTPUT_PROPERTIES = { - id: { type: 'string', description: 'User ID' }, - email: { type: 'string', description: 'User email address' }, - roleId: { type: 'string', description: 'User role ID', optional: true }, - primaryTeamId: { type: 'string', description: 'Primary team ID', optional: true }, - secondaryTeamIds: { - type: 'array', - description: 'Secondary team IDs', - optional: true, - items: { type: 'string', description: 'Team ID' }, +export const NOTE_PROPERTIES_OUTPUT = { + hs_note_body: { type: 'string', description: 'Note text/body (supports rich text/HTML)' }, + hs_timestamp: { type: 'string', description: 'Note activity time (ISO 8601)' }, + hubspot_owner_id: { type: 'string', description: 'HubSpot owner ID' }, + hs_attachment_ids: { + type: 'string', + description: 'Semicolon-separated IDs of files attached to the note', + }, + hs_object_id: { type: 'string', description: 'HubSpot object ID (same as record ID)' }, + hs_createdate: { type: 'string', description: 'Note creation date (ISO 8601)' }, + hs_lastmodifieddate: { type: 'string', description: 'Last modified date (ISO 8601)' }, +} as const satisfies Record + +/** + * Common email engagement properties returned by HubSpot API. + * @see https://developers.hubspot.com/docs/api-reference/crm-emails-v3 + */ +export const EMAIL_PROPERTIES_OUTPUT = { + hs_timestamp: { type: 'string', description: 'Email activity time (ISO 8601)' }, + hs_email_direction: { + type: 'string', + description: 'Direction (EMAIL = outgoing, INCOMING_EMAIL, FORWARDED_EMAIL)', + }, + hs_email_status: { + type: 'string', + description: 'Send status (SENT, SENDING, SCHEDULED, FAILED, BOUNCED)', }, - superAdmin: { type: 'boolean', description: 'Whether user is a super admin', optional: true }, + hs_email_subject: { type: 'string', description: 'Email subject line' }, + hs_email_text: { type: 'string', description: 'Plain-text email body' }, + hs_email_html: { type: 'string', description: 'HTML email body' }, + hs_email_headers: { type: 'string', description: 'JSON-encoded from/to/cc/bcc headers' }, + hs_email_from_email: { type: 'string', description: 'Sender email address' }, + hs_email_to_email: { type: 'string', description: 'Recipient email address(es)' }, + 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: 'Email creation date (ISO 8601)' }, + hs_lastmodifieddate: { type: 'string', description: 'Last modified date (ISO 8601)' }, } as const satisfies Record /** - * Users array output definition for list endpoints. + * Note object output definition with nested properties. + */ +export const NOTE_OBJECT_OUTPUT: OutputProperty = { + type: 'object', + description: 'HubSpot note record', + properties: { + ...CRM_RECORD_BASE_OUTPUT_PROPERTIES, + properties: { + type: 'object', + description: 'Note properties', + properties: NOTE_PROPERTIES_OUTPUT, + }, + associations: { + type: 'object', + description: 'Associated records (contacts, companies, deals, etc.)', + optional: true, + }, + }, +} + +/** + * Notes array output definition for list/search endpoints. */ -export const USERS_ARRAY_OUTPUT: OutputProperty = { +export const NOTES_ARRAY_OUTPUT: OutputProperty = { type: 'array', - description: 'Array of HubSpot user objects', + description: 'Array of HubSpot note records', items: { type: 'object', - properties: USER_OUTPUT_PROPERTIES, + properties: { + ...CRM_RECORD_BASE_OUTPUT_PROPERTIES, + properties: { + type: 'object', + description: 'Note properties', + properties: NOTE_PROPERTIES_OUTPUT, + }, + associations: { type: 'object', description: 'Associated records', optional: true }, + }, }, } -// Common HubSpot types -interface HubSpotUser { - id: string - email: string - firstName?: string - lastName?: string - roleId?: string - primaryTeamId?: string - superAdmin?: boolean +/** + * Email object output definition with nested properties. + */ +export const EMAIL_OBJECT_OUTPUT: OutputProperty = { + type: 'object', + description: 'HubSpot email engagement record', + properties: { + ...CRM_RECORD_BASE_OUTPUT_PROPERTIES, + properties: { + type: 'object', + description: 'Email properties', + properties: EMAIL_PROPERTIES_OUTPUT, + }, + associations: { + type: 'object', + description: 'Associated records (contacts, companies, deals, etc.)', + optional: true, + }, + }, +} + +/** + * Emails array output definition for list/search endpoints. + */ +export const EMAILS_ARRAY_OUTPUT: OutputProperty = { + type: 'array', + description: 'Array of HubSpot email engagement records', + items: { + type: 'object', + properties: { + ...CRM_RECORD_BASE_OUTPUT_PROPERTIES, + properties: { + type: 'object', + description: 'Email properties', + properties: EMAIL_PROPERTIES_OUTPUT, + }, + associations: { type: 'object', description: 'Associated records', optional: true }, + }, + }, +} + +/** + * Property option output (picklist/enumeration value). + * @see https://developers.hubspot.com/docs/api-reference/crm-properties-v3 + */ +export const PROPERTY_OPTION_OUTPUT_PROPERTIES = { + label: { type: 'string', description: 'Human-readable option label' }, + value: { type: 'string', description: 'Internal value used when setting the property' }, + displayOrder: { type: 'number', description: 'Display order (-1 sorts last)', optional: true }, + hidden: { type: 'boolean', description: 'Whether the option is hidden in the HubSpot UI' }, + description: { type: 'string', description: 'Option description', optional: true }, +} as const satisfies Record + +/** + * Property definitions array output for the properties endpoint. + */ +export const PROPERTIES_ARRAY_OUTPUT: OutputProperty = { + type: 'array', + description: 'Array of HubSpot property definitions', + items: { + type: 'object', + properties: { + name: { type: 'string', description: 'Internal property name' }, + label: { type: 'string', description: 'Human-readable label' }, + type: { + type: 'string', + description: 'Property data type (string, number, enumeration, bool, datetime, etc.)', + }, + fieldType: { type: 'string', description: 'Field type controlling HubSpot UI rendering' }, + description: { type: 'string', description: 'Property help text' }, + groupName: { type: 'string', description: 'Property group the property belongs to' }, + options: { + type: 'array', + description: 'Enumeration/picklist options (empty for non-enumerated properties)', + items: { type: 'object', properties: PROPERTY_OPTION_OUTPUT_PROPERTIES }, + }, + displayOrder: { type: 'number', description: 'Display order', optional: true }, + calculated: { + type: 'boolean', + description: 'Whether the property is calculated by HubSpot', + optional: true, + }, + hidden: { type: 'boolean', description: 'Whether the property is hidden', optional: true }, + hubspotDefined: { + type: 'boolean', + description: 'Whether the property is a HubSpot default property', + optional: true, + }, + archived: { + type: 'boolean', + description: 'Whether the property is archived', + optional: true, + }, + }, + }, +} + +/** + * Associations array output for the v4 associations list endpoint. + */ +export const ASSOCIATIONS_ARRAY_OUTPUT: OutputProperty = { + type: 'array', + description: 'Array of associated records', + items: { + type: 'object', + properties: { + toObjectId: { type: 'string', description: 'ID of the associated (target) record' }, + associationTypes: { + type: 'array', + description: 'Association types linking the two records', + items: { + type: 'object', + properties: { + category: { + type: 'string', + description: + 'Association category (HUBSPOT_DEFINED, USER_DEFINED, INTEGRATOR_DEFINED)', + }, + typeId: { type: 'number', description: 'Association type ID' }, + label: { type: 'string', description: 'Association label', optional: true }, + }, + }, + }, + }, + }, } +// Common HubSpot types interface HubSpotCrmObject { id: string properties: Record @@ -744,7 +913,7 @@ interface HubSpotPaging { // Users export interface HubSpotGetUsersResponse extends ToolResponse { output: { - users: HubSpotUser[] + users: HubSpotCrmObject[] paging: HubSpotPaging | null totalItems?: number success: boolean @@ -755,6 +924,7 @@ export interface HubSpotGetUsersParams { accessToken: string limit?: string after?: string + properties?: string } // List Contacts @@ -1216,6 +1386,152 @@ export interface HubSpotCreateListResponse extends ToolResponse { } } +// Notes +export type HubSpotNote = HubSpotCrmObject +export type HubSpotCreateNoteParams = HubSpotCreateContactParams +export type HubSpotCreateNoteResponse = ToolResponse & { + output: { note: HubSpotContact; noteId: string; success: boolean } +} +export type HubSpotGetNoteParams = Omit & { noteId: string } +export type HubSpotGetNoteResponse = ToolResponse & { + output: { note: HubSpotContact; noteId: string; success: boolean } +} +export type HubSpotListNotesParams = HubSpotListContactsParams +export type HubSpotListNotesResponse = ToolResponse & { + output: { + notes: HubSpotContact[] + paging?: HubSpotPaging + metadata: { totalReturned: number; hasMore: boolean } + success: boolean + } +} +export type HubSpotSearchNotesParams = HubSpotSearchContactsParams +export interface HubSpotSearchNotesResponse extends ToolResponse { + output: { + notes: HubSpotContact[] + total: number + paging?: HubSpotPaging + metadata: { totalReturned: number; hasMore: boolean } + success: boolean + } +} + +// Emails +export type HubSpotEmail = HubSpotCrmObject +export type HubSpotCreateEmailParams = HubSpotCreateContactParams +export type HubSpotCreateEmailResponse = ToolResponse & { + output: { email: HubSpotContact; emailId: string; success: boolean } +} +export type HubSpotGetEmailParams = Omit & { emailId: string } +export type HubSpotGetEmailResponse = ToolResponse & { + output: { email: HubSpotContact; emailId: string; success: boolean } +} +export type HubSpotListEmailsParams = HubSpotListContactsParams +export type HubSpotListEmailsResponse = ToolResponse & { + output: { + emails: HubSpotContact[] + paging?: HubSpotPaging + metadata: { totalReturned: number; hasMore: boolean } + success: boolean + } +} +export type HubSpotSearchEmailsParams = HubSpotSearchContactsParams +export interface HubSpotSearchEmailsResponse extends ToolResponse { + output: { + emails: HubSpotContact[] + total: number + paging?: HubSpotPaging + metadata: { totalReturned: number; hasMore: boolean } + success: boolean + } +} + +// Properties +interface HubSpotPropertyOption { + label: string + value: string + displayOrder?: number + hidden: boolean + description?: string +} + +interface HubSpotProperty { + name: string + label: string + type: string + fieldType: string + description: string + groupName: string + options: HubSpotPropertyOption[] + displayOrder?: number + calculated?: boolean + hidden?: boolean + hubspotDefined?: boolean + archived?: boolean +} + +export interface HubSpotGetPropertiesParams { + accessToken: string + objectType: string + propertyName?: string + archived?: boolean +} + +export interface HubSpotGetPropertiesResponse extends ToolResponse { + output: { + properties: HubSpotProperty[] + metadata: { totalReturned: number; objectType: string } + success: boolean + } +} + +// Associations (v4) +interface HubSpotAssociatedObject { + toObjectId: string + associationTypes: Array<{ + category: string + typeId: number + label?: string + }> +} + +export interface HubSpotListAssociationsParams { + accessToken: string + objectType: string + objectId: string + toObjectType: string + limit?: string + after?: string +} + +export interface HubSpotListAssociationsResponse extends ToolResponse { + output: { + results: HubSpotAssociatedObject[] + paging?: HubSpotPaging + metadata: { totalReturned: number; hasMore: boolean } + success: boolean + } +} + +export interface HubSpotCreateAssociationParams { + accessToken: string + objectType: string + objectId: string + toObjectType: string + toObjectId: string + associationCategory?: string + associationTypeId?: number +} + +export interface HubSpotCreateAssociationResponse extends ToolResponse { + output: { + fromObjectId: string + toObjectId: string + labels: string[] + success: boolean + } +} + // Generic HubSpot response type for the block export type HubSpotResponse = | HubSpotGetUsersResponse @@ -1257,3 +1573,14 @@ export type HubSpotResponse = | HubSpotListListsResponse | HubSpotGetListResponse | HubSpotCreateListResponse + | HubSpotCreateNoteResponse + | HubSpotGetNoteResponse + | HubSpotListNotesResponse + | HubSpotSearchNotesResponse + | HubSpotCreateEmailResponse + | HubSpotGetEmailResponse + | HubSpotListEmailsResponse + | HubSpotSearchEmailsResponse + | HubSpotGetPropertiesResponse + | HubSpotListAssociationsResponse + | HubSpotCreateAssociationResponse diff --git a/apps/sim/tools/hubspot/update_appointment.ts b/apps/sim/tools/hubspot/update_appointment.ts index 54854d1aadc..18028d82e9d 100644 --- a/apps/sim/tools/hubspot/update_appointment.ts +++ b/apps/sim/tools/hubspot/update_appointment.ts @@ -46,7 +46,7 @@ export const hubspotUpdateAppointmentTool: ToolConfig< 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"})', + 'Appointment properties to update as JSON object (e.g., {"hs_appointment_name": "Updated Call", "hs_appointment_start": "2024-01-15T10:00:00Z"})', }, }, diff --git a/apps/sim/tools/registry.ts b/apps/sim/tools/registry.ts index 18a16223e61..c8f7422f592 100644 --- a/apps/sim/tools/registry.ts +++ b/apps/sim/tools/registry.ts @@ -1415,37 +1415,48 @@ import { import { httpRequestTool, webhookRequestTool } from '@/tools/http' import { hubspotCreateAppointmentTool, + hubspotCreateAssociationTool, hubspotCreateCompanyTool, hubspotCreateContactTool, hubspotCreateDealTool, + hubspotCreateEmailTool, hubspotCreateLineItemTool, hubspotCreateListTool, + hubspotCreateNoteTool, hubspotCreateTicketTool, hubspotGetAppointmentTool, hubspotGetCartTool, hubspotGetCompanyTool, hubspotGetContactTool, hubspotGetDealTool, + hubspotGetEmailTool, hubspotGetLineItemTool, hubspotGetListTool, hubspotGetMarketingEventTool, + hubspotGetNoteTool, + hubspotGetPropertiesTool, hubspotGetQuoteTool, hubspotGetTicketTool, hubspotGetUsersTool, hubspotListAppointmentsTool, + hubspotListAssociationsTool, hubspotListCartsTool, hubspotListCompaniesTool, hubspotListContactsTool, hubspotListDealsTool, + hubspotListEmailsTool, hubspotListLineItemsTool, hubspotListListsTool, hubspotListMarketingEventsTool, + hubspotListNotesTool, hubspotListOwnersTool, hubspotListQuotesTool, hubspotListTicketsTool, hubspotSearchCompaniesTool, hubspotSearchContactsTool, hubspotSearchDealsTool, + hubspotSearchEmailsTool, + hubspotSearchNotesTool, hubspotSearchTicketsTool, hubspotUpdateAppointmentTool, hubspotUpdateCompanyTool, @@ -6468,37 +6479,48 @@ export const tools: Record = { infisical_update_secret: infisicalUpdateSecretTool, infisical_delete_secret: infisicalDeleteSecretTool, hubspot_create_appointment: hubspotCreateAppointmentTool, + hubspot_create_association: hubspotCreateAssociationTool, hubspot_create_company: hubspotCreateCompanyTool, hubspot_create_contact: hubspotCreateContactTool, hubspot_create_deal: hubspotCreateDealTool, + hubspot_create_email: hubspotCreateEmailTool, hubspot_create_line_item: hubspotCreateLineItemTool, hubspot_create_list: hubspotCreateListTool, + hubspot_create_note: hubspotCreateNoteTool, 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_email: hubspotGetEmailTool, hubspot_get_line_item: hubspotGetLineItemTool, hubspot_get_list: hubspotGetListTool, hubspot_get_marketing_event: hubspotGetMarketingEventTool, + hubspot_get_note: hubspotGetNoteTool, + hubspot_get_properties: hubspotGetPropertiesTool, hubspot_get_quote: hubspotGetQuoteTool, hubspot_get_ticket: hubspotGetTicketTool, hubspot_get_users: hubspotGetUsersTool, hubspot_list_appointments: hubspotListAppointmentsTool, + hubspot_list_associations: hubspotListAssociationsTool, hubspot_list_carts: hubspotListCartsTool, hubspot_list_companies: hubspotListCompaniesTool, hubspot_list_contacts: hubspotListContactsTool, hubspot_list_deals: hubspotListDealsTool, + hubspot_list_emails: hubspotListEmailsTool, hubspot_list_line_items: hubspotListLineItemsTool, hubspot_list_lists: hubspotListListsTool, hubspot_list_marketing_events: hubspotListMarketingEventsTool, + hubspot_list_notes: hubspotListNotesTool, 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_emails: hubspotSearchEmailsTool, + hubspot_search_notes: hubspotSearchNotesTool, hubspot_search_tickets: hubspotSearchTicketsTool, hubspot_update_appointment: hubspotUpdateAppointmentTool, hubspot_update_company: hubspotUpdateCompanyTool, From e15da0ee19f7a21fe1871a29ef85a4235c13c526 Mon Sep 17 00:00:00 2001 From: waleed Date: Sat, 13 Jun 2026 17:22:39 -0700 Subject: [PATCH 2/3] fix(hubspot): drop non-grantable notes/emails scopes; remove inline comments - crm.objects.notes.*/crm.objects.emails.* are not grantable HubSpot scopes (would break the OAuth authorize flow). Notes/emails engagement and association endpoints are authorized by crm.objects.contacts.*; sales-email-read remains for email-engagement content - Remove non-TSDoc inline comments from new tool files --- apps/sim/lib/oauth/oauth.ts | 4 ---- apps/sim/lib/oauth/utils.ts | 4 ---- apps/sim/tools/hubspot/create_association.ts | 4 ---- apps/sim/tools/hubspot/get_properties.ts | 2 -- 4 files changed, 14 deletions(-) diff --git a/apps/sim/lib/oauth/oauth.ts b/apps/sim/lib/oauth/oauth.ts index 8c1594aa5b9..dc8eed7cc8c 100644 --- a/apps/sim/lib/oauth/oauth.ts +++ b/apps/sim/lib/oauth/oauth.ts @@ -917,10 +917,6 @@ export const OAUTH_PROVIDERS: Record = { 'crm.objects.appointments.read', 'crm.objects.appointments.write', 'crm.objects.carts.read', - 'crm.objects.notes.read', - 'crm.objects.notes.write', - 'crm.objects.emails.read', - 'crm.objects.emails.write', 'sales-email-read', 'crm.lists.read', 'crm.lists.write', diff --git a/apps/sim/lib/oauth/utils.ts b/apps/sim/lib/oauth/utils.ts index cf92ae13f42..d9c4d76a61e 100644 --- a/apps/sim/lib/oauth/utils.ts +++ b/apps/sim/lib/oauth/utils.ts @@ -314,10 +314,6 @@ export const SCOPE_DESCRIPTIONS: Record = { 'crm.objects.appointments.write': 'Create and update HubSpot appointments', 'crm.objects.carts.read': 'Read HubSpot shopping carts', 'crm.objects.carts.write': 'Create and update HubSpot shopping carts', - 'crm.objects.notes.read': 'Read HubSpot notes', - 'crm.objects.notes.write': 'Create and update HubSpot notes', - 'crm.objects.emails.read': 'Read HubSpot email engagements', - 'crm.objects.emails.write': 'Create and update HubSpot email engagements', 'sales-email-read': 'Read the content of HubSpot email engagements', 'crm.import': 'Import data into HubSpot', 'crm.lists.read': 'Read HubSpot lists', diff --git a/apps/sim/tools/hubspot/create_association.ts b/apps/sim/tools/hubspot/create_association.ts index 2a531b6b88d..37591c29d10 100644 --- a/apps/sim/tools/hubspot/create_association.ts +++ b/apps/sim/tools/hubspot/create_association.ts @@ -74,7 +74,6 @@ export const hubspotCreateAssociationTool: ToolConfig< const from = `${encodeURIComponent(params.objectType.trim())}/${encodeURIComponent(params.objectId.trim())}` const to = `${encodeURIComponent(params.toObjectType.trim())}/${encodeURIComponent(params.toObjectId.trim())}` - // A specific type ID means a labeled association; otherwise create the default. return params.associationTypeId != null ? `https://api.hubapi.com/crm/v4/objects/${from}/associations/${to}` : `https://api.hubapi.com/crm/v4/objects/${from}/associations/default/${to}` @@ -91,7 +90,6 @@ export const hubspotCreateAssociationTool: ToolConfig< } }, body: (params) => { - // The default-association endpoint takes no body. if (params.associationTypeId == null) { return undefined } @@ -113,8 +111,6 @@ export const hubspotCreateAssociationTool: ToolConfig< throw new Error(data.message || 'Failed to create association in HubSpot') } - // Labeled associations return LabelsBetweenObjectPair; default associations - // return a batch response with a single result. const batchResult = Array.isArray(data.results) ? data.results[0] : undefined return { diff --git a/apps/sim/tools/hubspot/get_properties.ts b/apps/sim/tools/hubspot/get_properties.ts index 527fd1555e5..ef601eb3f12 100644 --- a/apps/sim/tools/hubspot/get_properties.ts +++ b/apps/sim/tools/hubspot/get_properties.ts @@ -88,8 +88,6 @@ export const hubspotGetPropertiesTool: ToolConfig< throw new Error(data.message || 'Failed to get properties from HubSpot') } - // The list endpoint returns { results: Property[] }; the single-property - // endpoint returns the Property object directly. const properties = Array.isArray(data.results) ? data.results : [data] return { From 73b42dbcb3d95886936c57d4866aa24d3820c83a Mon Sep 17 00:00:00 2001 From: waleed Date: Sat, 13 Jun 2026 17:26:33 -0700 Subject: [PATCH 3/3] fix(hubspot): address review comments - Forward the properties param for the Get Users operation (block param mapping + Properties-to-Return field now include get_users) - Use Record for create_note/create_email request bodies - Only send Content-Type on create_association when a body is sent (default-association PUT has no body) - Remove stray duplicate JSDoc opener in types.ts --- apps/sim/blocks/blocks/hubspot.ts | 2 ++ apps/sim/tools/hubspot/create_association.ts | 7 +++++-- apps/sim/tools/hubspot/create_email.ts | 2 +- apps/sim/tools/hubspot/create_note.ts | 2 +- apps/sim/tools/hubspot/types.ts | 1 - 5 files changed, 9 insertions(+), 5 deletions(-) diff --git a/apps/sim/blocks/blocks/hubspot.ts b/apps/sim/blocks/blocks/hubspot.ts index 6eb49dd6eb9..367ee0a4004 100644 --- a/apps/sim/blocks/blocks/hubspot.ts +++ b/apps/sim/blocks/blocks/hubspot.ts @@ -509,6 +509,7 @@ Return ONLY the JSON object with properties - no explanations, no markdown, no e 'get_quotes', 'get_appointments', 'get_carts', + 'get_users', ], }, }, @@ -1301,6 +1302,7 @@ Return ONLY the JSON array of property names - no explanations, no markdown, no 'get_quotes', 'get_appointments', 'get_carts', + 'get_users', ] if (properties && !searchProperties && getListOps.includes(operation as string)) { cleanParams.properties = properties diff --git a/apps/sim/tools/hubspot/create_association.ts b/apps/sim/tools/hubspot/create_association.ts index 37591c29d10..8454543ba09 100644 --- a/apps/sim/tools/hubspot/create_association.ts +++ b/apps/sim/tools/hubspot/create_association.ts @@ -84,10 +84,13 @@ export const hubspotCreateAssociationTool: ToolConfig< throw new Error('Access token is required') } - return { + const headers: Record = { Authorization: `Bearer ${params.accessToken}`, - 'Content-Type': 'application/json', } + if (params.associationTypeId != null) { + headers['Content-Type'] = 'application/json' + } + return headers }, body: (params) => { if (params.associationTypeId == null) { diff --git a/apps/sim/tools/hubspot/create_email.ts b/apps/sim/tools/hubspot/create_email.ts index c1f508183cb..16033c8736c 100644 --- a/apps/sim/tools/hubspot/create_email.ts +++ b/apps/sim/tools/hubspot/create_email.ts @@ -66,7 +66,7 @@ export const hubspotCreateEmailTool: ToolConfig< } } - const body: any = { + const body: Record = { properties, } diff --git a/apps/sim/tools/hubspot/create_note.ts b/apps/sim/tools/hubspot/create_note.ts index 0e279dbbc96..101ba8879f8 100644 --- a/apps/sim/tools/hubspot/create_note.ts +++ b/apps/sim/tools/hubspot/create_note.ts @@ -66,7 +66,7 @@ export const hubspotCreateNoteTool: ToolConfig = { properties, } diff --git a/apps/sim/tools/hubspot/types.ts b/apps/sim/tools/hubspot/types.ts index 448ac87bf33..b5ff425102d 100644 --- a/apps/sim/tools/hubspot/types.ts +++ b/apps/sim/tools/hubspot/types.ts @@ -678,7 +678,6 @@ export const LISTS_ARRAY_OUTPUT: OutputProperty = { }, } -/** /** * Common note properties returned by HubSpot API. * @see https://developers.hubspot.com/docs/api-reference/crm-notes-v3