Skip to content

feat(hubspot): add 27 CRM tools and fix OAuth scope mismatch#3765

Merged
waleedlatif1 merged 10 commits intostagingfrom
waleedlatif1/fix-hubspot-oauth
Mar 25, 2026
Merged

feat(hubspot): add 27 CRM tools and fix OAuth scope mismatch#3765
waleedlatif1 merged 10 commits intostagingfrom
waleedlatif1/fix-hubspot-oauth

Conversation

@waleedlatif1
Copy link
Collaborator

Summary

  • Added 27 new HubSpot CRM tools: deals (create, get, search, update), tickets (create, get, list, search, update), line items (create, get, list, update), quotes (get, list), appointments (create, get, list, update), carts (get, list), owners (list), marketing events (get, list), lists (create, get, list)
  • Fixed OAuth scope mismatch causing "Authorization failed" error: removed crm.objects.tickets.read and crm.objects.tickets.write (not registered in HubSpot app config), kept legacy tickets scope
  • Fixed HubSpot connector requiredScopes to use tickets instead of crm.objects.tickets.read
  • All 24 scopes validated against HubSpot app config and API docs

Type of Change

  • New feature
  • Bug fix

Testing

Tested manually

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

@cursor
Copy link

cursor bot commented Mar 25, 2026

PR Summary

Medium Risk
Adds many new HubSpot tool endpoints and updates block parameter mapping, plus changes OAuth scopes; risk is mainly integration/runtime regressions (wrong params, paging, or missing scopes) rather than core security logic.

Overview
Expands the HubSpot integration from contacts/companies/deals listing into a broader CRM surface area. The HubSpot block now supports create/get/update/search for deals, tickets, line items, and appointments; list/get for quotes, carts, owners, marketing events, and lists; and create for lists, with updated parameter conditioning and output shapes.

Adds the corresponding HubSpot tool implementations and types. New tools issue the appropriate HubSpot API requests (including pagination and optional associations/properties), extend types.ts with object/array output schemas, and register everything in the tool registry.

Fixes OAuth scope mismatches for tickets and improves small API ergonomics. Ticket scope is switched to legacy tickets in the connector and OAuth config, hubspot_get_users gains paging support, contact/company IDs are trimmed before use, and docs are updated (HubSpot deal list default limit and Quiver multi-file SVG outputs).

Written by Cursor Bugbot for commit 0bcdc49. Configure here.

@vercel
Copy link

vercel bot commented Mar 25, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Mar 25, 2026 8:14pm

Request Review

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 25, 2026

Greptile Summary

This PR adds 27 new HubSpot CRM tools (deals, tickets, line items, quotes, appointments, carts, owners, marketing events, lists) and fixes a critical OAuth scope mismatch that was causing "Authorization failed" errors by removing unregistered crm.objects.tickets.read/write scopes and keeping the legacy tickets scope.

Key changes:

  • OAuth fix: Removes two unregistered scopes from both oauth.ts and the connector's requiredScopes, resolving the authorization failure. The 24 scopes now in place have been validated against the HubSpot app config.
  • 27 new tools: All follow the established pattern with consistent use of shared output schemas (PAGING_OUTPUT, METADATA_OUTPUT, typed property schemas). The block dispatch correctly routes to single-record tools when an ID is provided and list tools otherwise.
  • HubSpotCrmObject refactor: A new base type is introduced and HubSpotContact is deprecated to an alias — however, several of the new response types still reference the deprecated alias for their output fields instead of the specific named types like HubSpotTicket or HubSpotDeal.
  • Minor type inconsistencies: search_deals/search_tickets return total: data.total ?? null but declare total: number; and list_lists returns metadata.total from the Lists API but this field is absent from the declared METADATA_OUTPUT schema, making it invisible to downstream workflow output pickers.

Confidence Score: 4/5

  • Safe to merge with minor type cleanup follow-ups; the critical OAuth fix is correct and all 27 new tools follow established patterns.
  • The OAuth scope fix is precise and correctly resolves the authorization failure. All 27 new tools are consistent with the existing codebase patterns, use correct HubSpot API endpoints, and the previous review concerns have been fully addressed. The remaining issues are type-only inconsistencies (deprecated alias usage, total: null vs number) that won't cause runtime failures in practice since HubSpot search always returns a total. The metadata.total omission from the Lists schema is the most practical concern since it affects the workflow output picker's discoverability.
  • apps/sim/tools/hubspot/types.ts (deprecated type references in new definitions), apps/sim/tools/hubspot/list_lists.ts (metadata.total not in output schema)

Important Files Changed

Filename Overview
apps/sim/tools/hubspot/types.ts Large expansion of output schemas and TypeScript types for 27 new tools; introduces HubSpotCrmObject as the new canonical base type, deprecates HubSpotContact, but several new response output fields still reference the deprecated type instead of the specific aliases.
apps/sim/lib/oauth/oauth.ts Removes crm.objects.tickets.read and crm.objects.tickets.write (unregistered scopes causing OAuth failures) and keeps the legacy tickets scope — a correct, targeted bug fix.
apps/sim/blocks/blocks/hubspot.ts Adds 20+ new operation options and their associated UI fields; the dispatch logic correctly routes to single-record tools when an ID is provided and list tools otherwise; get_lists correctly remaps limitcount and afteroffset for the legacy Lists API pagination.
apps/sim/tools/hubspot/search_deals.ts Implements HubSpot CRM search for deals using POST /crm/v3/objects/deals/search; consistent with search_contacts pattern; minor type mismatch — total can be null in implementation but typed as number.
apps/sim/tools/hubspot/search_tickets.ts Mirrors search_deals.ts for tickets; same total: null type mismatch; otherwise correct implementation against /crm/v3/objects/tickets/search.
apps/sim/tools/hubspot/list_lists.ts Uses POST /crm/v3/lists/search with offset-based pagination correctly mapped; returns metadata.total from the Lists API but METADATA_OUTPUT schema doesn't declare this field, so it's invisible to downstream workflow consumers.
apps/sim/tools/registry.ts All 27 new tools correctly imported and registered with consistent snake_case keys; no gaps between index.ts exports and registry entries.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[HubSpot Block\noperation param] --> B{Switch on operation}

    B --> C[Contacts\nget/create/update/search]
    B --> D[Companies\nget/create/update/search/list]
    B --> E{get_deals\ndealId?}
    B --> F[create_deal / update_deal / search_deals]
    B --> G{get_tickets\nticketId?}
    B --> H[create_ticket / update_ticket / search_tickets]
    B --> I{get_line_items\nlineItemId?}
    B --> J[create_line_item / update_line_item]
    B --> K{get_quotes\nquoteId?}
    B --> L{get_appointments\nappointmentId?}
    B --> M[create_appointment / update_appointment]
    B --> N{get_carts\ncartId?}
    B --> O[list_owners]
    B --> P{get_marketing_events\neventId?}
    B --> Q{get_lists\nlistId?}
    B --> R[create_list]
    B --> S[get_users]

    E -->|yes| E1[hubspot_get_deal\nGET /crm/v3/objects/deals/:id]
    E -->|no| E2[hubspot_list_deals\nGET /crm/v3/objects/deals]
    G -->|yes| G1[hubspot_get_ticket\nGET /crm/v3/objects/tickets/:id]
    G -->|no| G2[hubspot_list_tickets\nGET /crm/v3/objects/tickets]
    I -->|yes| I1[hubspot_get_line_item]
    I -->|no| I2[hubspot_list_line_items]
    K -->|yes| K1[hubspot_get_quote]
    K -->|no| K2[hubspot_list_quotes]
    L -->|yes| L1[hubspot_get_appointment]
    L -->|no| L2[hubspot_list_appointments]
    N -->|yes| N1[hubspot_get_cart]
    N -->|no| N2[hubspot_list_carts]
    P -->|yes| P1[hubspot_get_marketing_event\nGET /marketing/v3/marketing-events/:id]
    P -->|no| P2[hubspot_list_marketing_events\nGET /marketing/v3/marketing-events]
    Q -->|yes| Q1[hubspot_get_list\nGET /crm/v3/lists/:id]
    Q -->|no| Q2[hubspot_list_lists\nPOST /crm/v3/lists/search]
Loading

Reviews (4): Last reviewed commit: "fix(hubspot): return paging in get_users..." | Re-trigger Greptile

@waleedlatif1
Copy link
Collaborator Author

Addressed both review findings:

P1 (Marketing Events appId): Switched both get_marketing_event.ts and list_marketing_events.ts to use the CRM Objects API (/crm/v3/objects/marketing_events) instead of the Marketing Events API. This uses internal object IDs and doesn't require appId.

P2 (Type aliases): Introduced HubSpotCrmObject as the base interface and updated all entity type aliases (HubSpotCompany, HubSpotDeal, HubSpotTicket, etc.) to alias from it instead of HubSpotContact. HubSpotContact is kept as a deprecated alias for backward compat.

Both fixed in cbc42a3.

@waleedlatif1
Copy link
Collaborator Author

@greptile

@waleedlatif1
Copy link
Collaborator Author

@cursor review

…trim, descriptions

- Switch marketing event outputs to CRM envelope structure (id, properties, createdAt, updatedAt, archived) matching CRM Objects API
- Fix list_lists pagination: add offset param, map offset-based response to paging structure
- Add .trim() to contactId/companyId in pre-existing get/update tools
- Fix default limit descriptions (100 → 10) in list_contacts/list_companies
- Fix operator examples (CONTAINS → CONTAINS_TOKEN) in search_contacts/search_companies
- Remove unused params arg in get_users transformResponse

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… per API docs

Marketing Events:
- Revert from /crm/v3/objects/marketing_events back to /marketing/v3/marketing-events
- The Marketing Events API does NOT require appId for GET /marketing-events/{objectId}
- appId is only needed for the /events/{externalEventId} endpoint (which we don't use)
- Restore flat response schema (objectId, eventName, etc. at top level, not CRM envelope)

Lists:
- POST /crm/v3/lists/search uses offset-based pagination (not cursor-based)
- Response shape: { lists, hasMore, offset, total } — not { results, paging }
- Map offset → paging.next.after for consistent block interface
- Fix default count: 20 (not 25), max 500
- GET /crm/v3/lists/{listId} wraps response in { list: { ... } }

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Revert list_contacts/list_companies default limit back to 100 (confirmed by API docs)
- Add idProperty param to get_appointment.ts (was missing, inconsistent with update_appointment)
- Remove get_carts from idProperty block condition (carts don't support idProperty)
- Add get_lists to after block condition (pagination was inaccessible from UI)
- Add after pagination param to get_users.ts (was missing, users beyond first page unreachable)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@waleedlatif1
Copy link
Collaborator Author

@greptile

@waleedlatif1
Copy link
Collaborator Author

@cursor review

…tion

- Add paging output to get_users transformResponse and outputs
- Add get_users to block after subBlock condition so cursor is accessible from UI

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@waleedlatif1
Copy link
Collaborator Author

@greptile

@waleedlatif1
Copy link
Collaborator Author

@cursor review

Use `?? 0` instead of `?? null` for search tools where the type declares
`total: number`. Also declare `total` in list_lists metadata output schema.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

@waleedlatif1 waleedlatif1 merged commit e0f2b8f into staging Mar 25, 2026
5 of 6 checks passed
@waleedlatif1 waleedlatif1 deleted the waleedlatif1/fix-hubspot-oauth branch March 25, 2026 20:15
Sg312 pushed a commit that referenced this pull request Mar 25, 2026
* feat(hubspot): add 27 CRM tools and fix OAuth scope mismatch

* lint

* fix(hubspot): switch marketing events to CRM Objects API and add HubSpotCrmObject base type

* chore(docs): fix import ordering and formatting lint errors

* feat(hubspot): wire all 27 new tools into block definition

* fix(hubspot): address review comments - schema mismatch, pagination, trim, descriptions

- Switch marketing event outputs to CRM envelope structure (id, properties, createdAt, updatedAt, archived) matching CRM Objects API
- Fix list_lists pagination: add offset param, map offset-based response to paging structure
- Add .trim() to contactId/companyId in pre-existing get/update tools
- Fix default limit descriptions (100 → 10) in list_contacts/list_companies
- Fix operator examples (CONTAINS → CONTAINS_TOKEN) in search_contacts/search_companies
- Remove unused params arg in get_users transformResponse

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(hubspot): revert to Marketing Events API and fix Lists pagination per API docs

Marketing Events:
- Revert from /crm/v3/objects/marketing_events back to /marketing/v3/marketing-events
- The Marketing Events API does NOT require appId for GET /marketing-events/{objectId}
- appId is only needed for the /events/{externalEventId} endpoint (which we don't use)
- Restore flat response schema (objectId, eventName, etc. at top level, not CRM envelope)

Lists:
- POST /crm/v3/lists/search uses offset-based pagination (not cursor-based)
- Response shape: { lists, hasMore, offset, total } — not { results, paging }
- Map offset → paging.next.after for consistent block interface
- Fix default count: 20 (not 25), max 500
- GET /crm/v3/lists/{listId} wraps response in { list: { ... } }

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(hubspot): final audit fixes verified against API docs

- Revert list_contacts/list_companies default limit back to 100 (confirmed by API docs)
- Add idProperty param to get_appointment.ts (was missing, inconsistent with update_appointment)
- Remove get_carts from idProperty block condition (carts don't support idProperty)
- Add get_lists to after block condition (pagination was inaccessible from UI)
- Add after pagination param to get_users.ts (was missing, users beyond first page unreachable)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(hubspot): return paging in get_users and add to block after condition

- Add paging output to get_users transformResponse and outputs
- Add get_users to block after subBlock condition so cursor is accessible from UI

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(hubspot): align total fallback with type definitions in search tools

Use `?? 0` instead of `?? null` for search tools where the type declares
`total: number`. Also declare `total` in list_lists metadata output schema.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant