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
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
| 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/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 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 target input.resource.type == "urn:connector:identos:hubspot". Per-object rules use the resource_id (e.g. hubspot://contacts/{id}).
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.0.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. |