Skip to main content
Version: Latest

HubSpot

Proxy for the HubSpot CRM v3 API via the managed gateway.

What this connector does

Gives agents gated access to a HubSpot account for:

  • Reading and writing Contacts, Companies, Deals, and Tickets.
  • Creating and reading Notes/engagements (activity logging).
  • Searching any CRM object type with filters or a free-text query.

All operations are gated by PBAC policy. Every call flows: agent → gateway → /introspect (with this connector's resource_type) → OPA decision → HubSpot API → response.

The connector supports two auth modes, chosen per managed resource at install time:

  • Per-user passthrough (idp_passthrough, recommended) — the gateway forwards each calling user's own HubSpot OAuth token (captured when they log in to PolicyArc through the HubSpot IdP). HubSpot enforces its own object-level permissions, and its audit shows the real person. HubSpot is OAuth 2.0 (not OIDC); PolicyArc resolves the user's identity via HubSpot's token introspection endpoint (/oauth/v3/token/introspect) — the bundled hubspot IdP preset wires this up.
  • Shared static token (static) — every call uses one operator-held HubSpot private-app token or service token via Authorization: Bearer. PolicyArc still enforces per-user policy and audit in front, but HubSpot sees a single app identity.

Create a HubSpot Projects app (for idp_passthrough)

Per-user passthrough needs an OAuth app with a configurable redirect URL and scopes. HubSpot has three app types; only a Projects app (created with the HubSpot CLI) gives all of Client ID + Client Secret + redirect URL + scopes.

Steps (verify exact commands against current HubSpot CLI docs — the tooling changes, and this is the confusing part):

  1. Install + authenticate the CLI: npm i -g @hubspot/cli, then hs init.
  2. Create a project with a public-app component: hs project create (pick an app template).
  3. In the app's config (*-hsmeta.json / public-app settings):
    • Auth: OAuth.
    • Redirect URL: <your-AS-issuer>/oauth2/callback.
    • Required scopes (the 8 below).
  4. hs project upload to deploy, then copy the app's Client ID and Client secret.

Then register a HubSpot IdP in PolicyArc (provider key hubspot) with that Client ID/secret — pick HubSpot in the IdP setup wizard and the bundled preset fills the endpoints (authorize, token, and the introspection endpoint used to resolve the user). Users must log in to PolicyArc through that IdP for passthrough to capture their token.

Prerequisites

  • A HubSpot account.
  • Passthrough mode: the Projects app + HubSpot IdP registration above.
  • Static mode: a HubSpot private-app token or service token with the scopes you want to gate, stored in a PolicyArc secret.

Install

Pick the auth mode in the install form's Authentication field (defaults to idp_passthrough). The static-token field only appears when static is selected; passthrough installs need no secret.

Verify

curl -X POST https://<your-as>/token \
-u "<agent-client-id>:<agent-client-secret>" \
-d "grant_type=client_credentials&scope=hs:contacts:read"

curl https://<your-as>/gateway/identos.hubspot/crm/v3/objects/contacts \
-H "Authorization: Bearer <token>"

A 200 with the contact list confirms: token issued → introspect allowed → gateway proxied → upstream responded.

What can go wrong

SymptomCauseFix
401 from HubSpot (static mode)Token wrong, expired, or missing the needed scopeCheck the secret value and the token's scopes.
403 from the gatewayAgent lacks the required scopeRequest a token with the hs:* scope matching the route.
404 at the gatewayWrong pathGateway path is /gateway/identos.hubspot/<HubSpot REST path> — HubSpot CRM paths start with /crm/v3/....
502 "no stored IdP token" (passthrough)The caller never logged in through the hubspot IdP (or used client_credentials, which has no user context)Re-authenticate via HubSpot.
Passthrough subject not resolvedThe OAuth app's token introspection didn't return a userEnsure it's a Projects app (user-level token) with the scopes below.

Scopes

ScopeMeaningRoutes
hs:contacts:read / hs:contacts:writeRead / mutate contactsget/list/create/update contacts
hs:companies:read / hs:companies:writeRead / mutate companiesget/list/create/update companies
hs:deals:read / hs:deals:writeRead / mutate dealsget/list/create/update deals
hs:tickets:read / hs:tickets:writeRead / mutate ticketsget/list/create/update tickets
hs:notes:read / hs:notes:writeRead / create notesget/create notes
hs:crm:searchSearch any CRM object typesearch_crm

HubSpot OAuth scopes to grant the Projects app (passthrough) — these map to the tools above:

oauth
crm.objects.contacts.read crm.objects.contacts.write
crm.objects.companies.read crm.objects.companies.write
crm.objects.deals.read crm.objects.deals.write
tickets

Scope notes (verified against a real HubSpot Projects app):

  • Tickets use the legacy tickets scope — standard apps do not offer granular crm.objects.tickets.read/write, and requesting them makes HubSpot reject the entire authorize with a scope mismatch (login can't complete).
  • Notes have no grantable scope on standard apps, yet create_note / get_note work without one — so no notes scope is requested. (If HubSpot ever rejects a note call on your hub, that's a per-hub permission issue, not a missing OAuth scope.)

Policy authors can target input.resource.type == "urn:connector:identos:hubspot". Per-object rules use the resource_id (e.g. hubspot://contacts/{id}).

Setup fields

FieldRequiredDefaultPurpose
upstream_auth.typeyesidp_passthroughAuthentication — idp_passthrough (per-user, default) or static (shared token).
token_envconditionalSecret holding a HubSpot private-app/service token. Static mode only; hidden when idp_passthrough is selected.

MCP tools

ToolScopeDescription
get_contact / list_contactshs:contacts:readGet / list contacts.
create_contact / update_contacths:contacts:writeCreate / update a contact.
get_company / list_companieshs:companies:readGet / list companies.
create_company / update_companyhs:companies:writeCreate / update a company.
get_deal / list_dealshs:deals:readGet / list deals.
create_deal / update_dealhs:deals:writeCreate / update a deal.
get_ticket / list_ticketshs:tickets:readGet / list tickets.
create_ticket / update_tickeths:tickets:writeCreate / update a ticket.
get_notehs:notes:readGet a note/engagement.
create_notehs:notes:writeCreate a note, optionally associated with CRM records.
search_crmhs:crm:searchSearch a CRM object type with filters/query.

The 19 tools are a deliberate, extensible subset of HubSpot's CRM API; more objects/endpoints can be added later via manifest edits (routes + tools + scopes).

Manifest reference

  • ID: identos.hubspot
  • Version: 1.0.0
  • Resource type: urn:connector:identos:hubspot
  • Capabilities: mcp

Supported auth modes

TypeDetails
idp_passthroughrequires IdP hubspot
staticscheme bearer; setup fields: token_env

Setup fields

IDLabelDefaultSecret?Notes
upstream_auth.typeAuthenticationidp_passthroughnoidp_passthrough forwards each user's own HubSpot OAuth token (recommended). static uses a single shared private-app/service token via Authorization: Bearer.
token_envStatic tokenyesPick a secret containing a HubSpot private-app or service token. Required only for the static auth mode. / shown when upstream_auth.type == 'static'

Scopes

Scope
hs:contacts:read
hs:contacts:write
hs:companies:read
hs:companies:write
hs:deals:read
hs:deals:write
hs:tickets:read
hs:tickets:write
hs:notes:read
hs:notes:write
hs:crm:search

Routes

MethodPatternScopeResource template
GET/crm/v3/objects/contacts/{id}hs:contacts:readhubspot://contacts/{{id}}
GET/crm/v3/objects/contactshs:contacts:readhubspot://contacts
POST/crm/v3/objects/contactshs:contacts:writehubspot://contacts
PATCH/crm/v3/objects/contacts/{id}hs:contacts:writehubspot://contacts/{{id}}
GET/crm/v3/objects/companies/{id}hs:companies:readhubspot://companies/{{id}}
GET/crm/v3/objects/companieshs:companies:readhubspot://companies
POST/crm/v3/objects/companieshs:companies:writehubspot://companies
PATCH/crm/v3/objects/companies/{id}hs:companies:writehubspot://companies/{{id}}
GET/crm/v3/objects/deals/{id}hs:deals:readhubspot://deals/{{id}}
GET/crm/v3/objects/dealshs:deals:readhubspot://deals
POST/crm/v3/objects/dealshs:deals:writehubspot://deals
PATCH/crm/v3/objects/deals/{id}hs:deals:writehubspot://deals/{{id}}
GET/crm/v3/objects/tickets/{id}hs:tickets:readhubspot://tickets/{{id}}
GET/crm/v3/objects/ticketshs:tickets:readhubspot://tickets
POST/crm/v3/objects/ticketshs:tickets:writehubspot://tickets
PATCH/crm/v3/objects/tickets/{id}hs:tickets:writehubspot://tickets/{{id}}
POST/crm/v3/objects/noteshs:notes:writehubspot://notes
GET/crm/v3/objects/notes/{id}hs:notes:readhubspot://notes/{{id}}
POST/crm/v3/objects/{objectType}/searchhs:crm:searchhubspot://{{objectType}}

MCP tools

NameScopeDescription
get_contacths:contacts:readGet a HubSpot contact by record ID.
list_contactshs:contacts:readList HubSpot contacts (cursor-paginated).
create_contacths:contacts:writeCreate a HubSpot contact.
update_contacths:contacts:writeUpdate a HubSpot contact.
get_companyhs:companies:readGet a HubSpot company by record ID.
list_companieshs:companies:readList HubSpot companies (cursor-paginated).
create_companyhs:companies:writeCreate a HubSpot company.
update_companyhs:companies:writeUpdate a HubSpot company.
get_dealhs:deals:readGet a HubSpot deal by record ID.
list_dealshs:deals:readList HubSpot deals (cursor-paginated).
create_dealhs:deals:writeCreate a HubSpot deal.
update_dealhs:deals:writeUpdate a HubSpot deal.
get_tickeths:tickets:readGet a HubSpot ticket by record ID.
list_ticketshs:tickets:readList HubSpot tickets (cursor-paginated).
create_tickeths:tickets:writeCreate a HubSpot ticket.
update_tickeths:tickets:writeUpdate a HubSpot ticket.
create_notehs:notes:writeCreate a note/engagement, optionally associated with CRM records.
get_notehs:notes:readGet a note/engagement by record ID.
search_crmhs:crm:searchSearch a CRM object type with filters/query.