Connecting HubSpot
A HubSpot connection syncs CRM records into a workspace of their own, so agents can search contacts, companies, deals, and tickets alongside your code and documents.
It is read-only and records-only. Every scope requested is a .read scope; nothing in this design writes back to HubSpot, and no write scope is ever asked for.
Authorize it
On Connections, add a HubSpot connection and pick the object types you want:
| Object type | HubSpot scope requested |
|---|---|
| Contacts | crm.objects.contacts.read |
| Companies | crm.objects.companies.read |
| Deals | crm.objects.deals.read |
| Tickets | tickets |
You are then sent to HubSpot to approve the install, and returned to the console when it completes.
Only the objects you pick are requested
Scopes follow your selection: a connection syncing only contacts asks only for contacts. That keeps the grant minimal, and it is also why the picker matters — HubSpot refuses an install URL whose scopes are not all configured on the deployment's app.
If authorization fails complaining about scopes, the object type you picked is not enabled on the HubSpot app your operator configured. That is an operator fix, not something you can change from here.
Where the records land
Each connection gets its own workspace, and records are indexed under a three-segment virtual path:
<connection>/<object_type>/<record_id>For example crm/deals/1001 for a connection named crm. The shape is deliberate: it is what access rules match on, so a single rule can restrict one object type of one connection without naming individual records.
They are private until you share them
A freshly synced connection carries no access labels. With access control enabled, that means nobody sees the records until you write a rule for them — the safe default when a CRM has just been connected to a shared workspace.
To share them, add a prefix rule on Folder access rules against the connection's workspace:
| Prefix | Effect |
|---|---|
crm/ | Everything from the crm connection. |
crm/deals/ | Only deals. |
crm/tickets/ | Only tickets — e.g. shared with support, while deals stay with sales. |
Give each rule the group:<id> label for the team that should see it.
Wildcards do not cross a slash
Access rules compile with literal separators, so crm/deals/* matches crm/deals/1001 but not crm/deals/a/b. Every path segment — including the record id — is escaped when the path is built, so a record id containing a slash cannot break out of the rule meant to restrict it.
Syncing
Use Sync now; nothing syncs on a schedule yet. The connections table reports each connection's last sync start, finish, and record count, so a sync in flight is distinguishable from one that never ran — and zero records is reported as a real outcome, separately from a failure.
Checking it worked
Access → Setup check runs five health checks specific to connections, each covering something that is legal but invisible from the outside:
| Check | Means |
|---|---|
| No access rule | The connection's workspace has no rule, so its records are indexed but nobody can see them. The most common surprise. |
| Scope not granted | The object type is configured but its scope was not approved. |
| Token expired | The OAuth token needs re-authorization. |
| Never synced | Authorized but never run. |
| Last sync failed | The most recent run errored. |
Start there before concluding a connection is broken — "no access rule" and "never synced" look identical from a search that returns nothing.
Disconnecting
Disconnecting revokes the refresh token at HubSpot, which invalidates every access token minted from it, and deletes the local copy. Already-indexed records remain searchable until you remove them — as with database sources, stopping a sync and deleting data are separate actions.