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 bundledhubspotIdP preset wires this up. - Shared static token (
static) — every call uses one operator-held HubSpot private-app token or service token viaAuthorization: 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):
- Install + authenticate the CLI:
npm i -g @hubspot/cli, thenhs init. - Create a project with a public-app component:
hs project create(pick an app template). - In the app's config (
*-hsmeta.json/ public-app settings):- Auth: OAuth.
- Redirect URL:
<your-AS-issuer>/oauth2/callback. - Required scopes (the 8 below).
hs project uploadto 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
The gateway path is /gateway/<resource name>/<HubSpot REST path>. The install
form names the resource after the connector id without its vendor prefix, and
the name cannot be edited, so the resource answers at /gateway/hubspot/...:
curl "https://<your-as>/gateway/hubspot/crm/v3/objects/contacts" \
-H "Authorization: Bearer <token>"
In idp_passthrough mode, the default, <token> must be a user token, issued
through an authorization-code login by a user who signed in with the hubspot
IdP. A 200 with the contact list means policy allowed the call and HubSpot accepted the
forwarded user token. A client_credentials token carries no user, so the
gateway answers 502 with "Upstream auth failed: token resolution requires an
authenticated subject". In static mode the gateway sends its configured token
itself, and an agent's client_credentials token is enough.
Getting tokens covers both grants.
What can go wrong
| Symptom | Cause | Fix |
|---|---|---|
401 from HubSpot (static mode) | Token wrong, expired, or missing the needed scope | Check the secret value and the token's scopes. |
403 from the gateway | Agent lacks the required scope | Request a token with the hs:* scope matching the route. |
404 at the gateway | Wrong path | Gateway path is /gateway/<resource name>/<HubSpot REST path>; a default install is named hubspot. HubSpot CRM paths start with /crm/v3/.... |
401 with "error": "idp_link_required" (passthrough) | The user has no linked hubspot token, or the linked token lacks a scope the connector now requires | Open the link_url from the response body and sign in again. |
502 "Upstream auth failed: token resolution requires an authenticated subject" (passthrough) | The token is a client_credentials token, which carries no user | Use a user token from an authorization-code login. |
| Passthrough subject not resolved | The OAuth app's token introspection didn't return a user | Ensure it's a Projects app (user-level token) with the scopes below. |
Scopes
| Scope | Meaning | Routes |
|---|---|---|
hs:contacts:read / hs:contacts:write | Read / mutate contacts | get/list/create/update contacts |
hs:companies:read / hs:companies:write | Read / mutate companies | get/list/create/update companies |
hs:deals:read / hs:deals:write | Read / mutate deals | get/list/create/update deals |
hs:tickets:read / hs:tickets:write | Read / mutate tickets | get/list/create/update tickets |
hs:notes:read / hs:notes:write | Read / create notes | get/create notes |
hs:crm:search | Search any CRM object type | search_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
ticketsscope — standard apps do not offer granularcrm.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_notework 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 scope a rule with input.context.rs_context.resource_type == "urn:connector:identos:hubspot". Per-object rules compare input.context.rs_context.resource_id with the bare object id path value, not with hubspot://contacts/.... search_crm has no object id in its path and carries the object type instead, such as contacts. The gateway passes the first path parameter the route's resource_id_template names (ConnectorV1Adapter.java).
Setup fields
| Field | Required | Default | Purpose |
|---|---|---|---|
upstream_auth.type | yes | idp_passthrough | Authentication — idp_passthrough (per-user, default) or static (shared token). |
token_env | conditional | — | Secret holding a HubSpot private-app/service token. Static mode only; hidden when idp_passthrough is selected. |
MCP tools
| Tool | Scope | Description |
|---|---|---|
get_contact / list_contacts | hs:contacts:read | Get / list contacts. |
create_contact / update_contact | hs:contacts:write | Create / update a contact. |
get_company / list_companies | hs:companies:read | Get / list companies. |
create_company / update_company | hs:companies:write | Create / update a company. |
get_deal / list_deals | hs:deals:read | Get / list deals. |
create_deal / update_deal | hs:deals:write | Create / update a deal. |
get_ticket / list_tickets | hs:tickets:read | Get / list tickets. |
create_ticket / update_ticket | hs:tickets:write | Create / update a ticket. |
get_note | hs:notes:read | Get a note/engagement. |
create_note | hs:notes:write | Create a note, optionally associated with CRM records. |
search_crm | hs:crm:search | Search 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.1.0 - Resource type:
urn:connector:identos:hubspot - Capabilities:
mcp
Supported auth modes
| Type | Details |
|---|---|
idp_passthrough | requires IdP hubspot |
static | scheme bearer; setup fields: token_env |
Setup fields
| ID | Label | Default | Secret? | Notes |
|---|---|---|---|---|
upstream_auth.type | Authentication | idp_passthrough | no | idp_passthrough forwards each user's own HubSpot OAuth token (recommended). static uses a single shared private-app/service token via Authorization: Bearer. |
token_env | Static token | — | yes | Pick 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
| Method | Pattern | Scope | Resource template |
|---|---|---|---|
GET | /crm/v3/objects/contacts/{id} | hs:contacts:read | hubspot://contacts/{{id}} |
GET | /crm/v3/objects/contacts | hs:contacts:read | hubspot://contacts |
POST | /crm/v3/objects/contacts | hs:contacts:write | hubspot://contacts |
PATCH | /crm/v3/objects/contacts/{id} | hs:contacts:write | hubspot://contacts/{{id}} |
GET | /crm/v3/objects/companies/{id} | hs:companies:read | hubspot://companies/{{id}} |
GET | /crm/v3/objects/companies | hs:companies:read | hubspot://companies |
POST | /crm/v3/objects/companies | hs:companies:write | hubspot://companies |
PATCH | /crm/v3/objects/companies/{id} | hs:companies:write | hubspot://companies/{{id}} |
GET | /crm/v3/objects/deals/{id} | hs:deals:read | hubspot://deals/{{id}} |
GET | /crm/v3/objects/deals | hs:deals:read | hubspot://deals |
POST | /crm/v3/objects/deals | hs:deals:write | hubspot://deals |
PATCH | /crm/v3/objects/deals/{id} | hs:deals:write | hubspot://deals/{{id}} |
GET | /crm/v3/objects/tickets/{id} | hs:tickets:read | hubspot://tickets/{{id}} |
GET | /crm/v3/objects/tickets | hs:tickets:read | hubspot://tickets |
POST | /crm/v3/objects/tickets | hs:tickets:write | hubspot://tickets |
PATCH | /crm/v3/objects/tickets/{id} | hs:tickets:write | hubspot://tickets/{{id}} |
POST | /crm/v3/objects/notes | hs:notes:write | hubspot://notes |
GET | /crm/v3/objects/notes/{id} | hs:notes:read | hubspot://notes/{{id}} |
POST | /crm/v3/objects/{objectType}/search | hs:crm:search | hubspot://{{objectType}} |
MCP tools
| Name | Scope | Description |
|---|---|---|
get_contact | hs:contacts:read | Get a HubSpot contact by record ID. |
list_contacts | hs:contacts:read | List HubSpot contacts (cursor-paginated). |
create_contact | hs:contacts:write | Create a HubSpot contact. |
update_contact | hs:contacts:write | Update a HubSpot contact. |
get_company | hs:companies:read | Get a HubSpot company by record ID. |
list_companies | hs:companies:read | List HubSpot companies (cursor-paginated). |
create_company | hs:companies:write | Create a HubSpot company. |
update_company | hs:companies:write | Update a HubSpot company. |
get_deal | hs:deals:read | Get a HubSpot deal by record ID. |
list_deals | hs:deals:read | List HubSpot deals (cursor-paginated). |
create_deal | hs:deals:write | Create a HubSpot deal. |
update_deal | hs:deals:write | Update a HubSpot deal. |
get_ticket | hs:tickets:read | Get a HubSpot ticket by record ID. |
list_tickets | hs:tickets:read | List HubSpot tickets (cursor-paginated). |
create_ticket | hs:tickets:write | Create a HubSpot ticket. |
update_ticket | hs:tickets:write | Update a HubSpot ticket. |
create_note | hs:notes:write | Create a note/engagement, optionally associated with CRM records. |
get_note | hs:notes:read | Get a note/engagement by record ID. |
search_crm | hs:crm:search | Search a CRM object type with filters/query. |
Send feedback about this page