HubSpot
Connect HubSpot, configure the recipe that decides which customer voice becomes signals, and see what is live while HubSpot ingestion rolls out.
HubSpot is a customer-voice source for nima. nima reads your portal, learns where your data lives with a per-portal recipe, and turns customer text (tickets, survey comments, deal reasons, meeting, call, email and note text) into signals. It also keeps a thin account index so beacons can show the ARR at stake. nima is read-only against HubSpot and never copies your CRM.
HubSpot ingestion status
Connecting HubSpot, scanning the portal and saving a recipe work today. Importing and syncing are rolling out: they run only once the nima team switches HubSpot ingestion on, so for now POST /v1/hubspot/import and POST /v1/hubspot/sync-now succeed but report queued: false and no HubSpot signals arrive.
Enabled by the nima team on request. Connect, scan, recipe and settings work now; importing customer voice is rolling out for all workspaces at once, so ask the nima team when you want it.
What this means in practice:
- You can connect and finish setup ahead of time. The recipe you confirm is kept.
- Nothing syncs on connect. Even when ingestion is on, a workspace needs a confirmed recipe and an Import press before the 15-minute sweep reads anything.
- No workspace control turns ingestion on. There is no route, MCP tool or admin-panel switch for it.
Every /v1/hubspot/... and /v1/integrations/hubspot... route needs a signed-in owner or admin session. The account routes under HubSpot accounts and ARR at stake need a signed-in session of any member. None of these routes accepts an API key or an OAuth token, and none is available over MCP.
Connect HubSpot
Connecting HubSpot is an owner or admin action, done from Integrations in the admin panel: open the HubSpot card, choose Connect HubSpot, pick the portal and approve. nima returns you to Integrations and the card shows the portal it is reading. The connect request is POST /v1/integrations/hubspot/connect, which returns the HubSpot authorization URL.
A workspace connects one HubSpot portal. The three required scopes must be granted; the eleven optional scopes can be declined, and a source whose scope was declined shows as not granted instead of failing.
| Scope | Required | What it lets nima read |
|---|---|---|
oauth | Yes | Basic HubSpot sign-in |
crm.objects.contacts.read | Yes | Contacts, and the meeting, call, note, task, communication and postal-mail activity attached to them |
crm.objects.companies.read | Yes | Companies, the account index |
crm.objects.deals.read | No | Deals, deal won and lost reasons |
crm.objects.tickets.read | No | Tickets |
crm.objects.feedback_submissions.read | No | Survey submissions (needs Service Hub Professional or Enterprise) |
crm.objects.owners.read | No | Account owners |
crm.objects.custom.read | No | Custom objects used as voice sources |
crm.schemas.contacts.read | No | Contact property definitions |
crm.schemas.companies.read | No | Company property definitions |
crm.schemas.deals.read | No | Deal property definitions |
crm.schemas.custom.read | No | Custom object property definitions |
conversations.read | No | Ticket conversation threads |
sales-email-read | No | Email bodies |
Reconnecting works two ways:
- Same portal. nima keeps the recipe, cursors and settings.
- Different portal. nima resets the recipe, sync state and settings, and purges the previous portal's account data.
If HubSpot access fails for good, the connection is marked invalid, runs stop, and the fix is to connect again.
Scan and the HubSpot recipe
A HubSpot recipe tells nima where your portal keeps account identity, ARR, segments and customer voice. Every portal is configured differently, so nima proposes a recipe from a scan and you confirm it before anything is imported.
You run setup from the HubSpot card in Integrations: Scan, then Continue to mapping to review the proposed recipe, then Continue to import to set the backfill window and caps and press Import. Each step calls the route named below.
- Scan.
POST /v1/hubspot/scanreads the shape of the portal: objects with record counts, property names, labels, types and fill rates, pipelines, owner count and lifecycle stages. A scan reads metadata only, never record values. - Propose.
POST /v1/hubspot/recipe/proposebuilds a recipe with heuristics and one model call. It never overwrites a recipe you have confirmed. - Review and confirm.
GET /v1/hubspot/recipereturns the stored recipe with its status, problems and the last cached scan; the evidence explaining each mapping comes back only fromPOST /v1/hubspot/recipe/propose.PUT /v1/hubspot/recipesaves it with{ recipe, confirm }. A body that is not{ recipe, confirm }returns 400 with up to 10 issues; a recipe that fails validation returns 400 with up to 20 issues, eachpath.to.field: message.confirm: truemakes it the recipe the sync runs from.
Segments group accounts as customer, partner, prospect, churned or other, and a segment can be tracked, which stores its accounts in the index. A prospect segment cannot be tracked, and neither can a catch-all segment, because nima never stores plain leads. Before a recipe is confirmed nima counts what the tracked segments would store and refuses above 10,000 tracked accounts with a 400.
Other recipe limits: 20 segments and 20 voice sources per recipe, and 10 conditions per rule.
HubSpot voice sources
A voice source is one stream of customer text that the recipe reads. The recipe starts from presets, which are switched off automatically when the scan shows the object or scope is unavailable. Each source has a budget weight that sets its share of the daily cap, and a minimum length in customer characters.
| Source id | Reads | Weight | Minimum characters | Product-content gate |
|---|---|---|---|---|
meetings | Meeting notes | 1.0 | 30 | On |
emails | Inbound emails | 0.9 | 30 | On |
tickets | Tickets with their conversation thread | 0.8 | 30 | Off |
surveys | Survey comments | 0.8 | 10 | Off |
calls | Completed calls with a body | 0.7 | 30 | On |
deal_lost | Lost-deal reasons | 0.7 | 20 | Off |
deal_won | Won-deal reasons | 0.5 | 20 | Off |
notes | Notes | 0.5 | 30 | On |
communications | Communications | 0.4 | 30 | On |
tasks | Tasks | 0.3 | 60 | On |
postal_mail | Postal mail | 0.3 | 30 | On |
Each source also sets an origin for its signals. The default, from_segment, marks a signal customer when the account is a customer or partner and external otherwise; a source can instead pin external or internal. Contact email addresses go to a contacts table only after a signal exists and are never copied into signal content, metadata or the knowledge graph.
Why a HubSpot record does not become a signal
A HubSpot record becomes a signal only if it passes a fixed funnel, and nima counts each drop by source and reason over the last 7 days in the connection status (GET /v1/hubspot/app/status). Counts are never stored beyond that window. The reasons, in funnel order:
| Reason | Meaning |
|---|---|
filtered_scope | The record is not tied to a tracked account, or to a contact that resolves to one |
too_short | Fewer customer characters than the source minimum after cleaning |
automated | Out-of-office, bounce or no-reply text |
calendar_boilerplate | Calendar invite text |
score_only | A survey score with no comment; nima stores it as an account NPS fact instead |
picklist_reason | A deal reason that is only a picklist value |
duplicate | Identical normalized text within 30 days; attached as evidence to the existing signal |
near_duplicate | Near-identical text within 30 days; attached as evidence to the existing signal |
no_product_content | The source has the product-content gate on and the text has none |
budget_held | The daily cap is reached; the cursor holds until capacity returns |
budget_expired | Held for more than 7 days, then dropped |
error | The record failed to process |
Cleaning strips HTML, quoted replies, signatures, footers, calendar text and AI-notetaker boilerplate before these checks run. Contacts and deletions are never signals.
HubSpot import, backfill and daily caps
Import starts the backfill for a confirmed recipe, and after that a sweep runs every 15 minutes. POST /v1/hubspot/import returns { queued, backfillFrom } and answers 409 until the recipe is confirmed. POST /v1/hubspot/sync-now returns { queued }, which is false when a run is already queued or running, or while ingestion is not switched on. After Import the card shows Edit mapping and Sync now.
Settings are saved with PUT /v1/hubspot/settings, which takes all three fields every time (dailySignalCap is a whole number):
| Setting | Values | Default | Effect |
|---|---|---|---|
backfillDays | 30, 90, 180 or 365 | 90 | How far back Import reads voice |
dailySignalCap | 0 to 5000 | 300 | Most signals created per UTC day |
dailyUsdCap | 0 to 100 | 3 | Estimated extraction spend per UTC day, in USD |
nima estimates each voice signal at $0.008 and stops at whichever cap is reached first. Each enabled source gets a share of the day's capacity by weight, so one busy source cannot starve the others.
HubSpot webhooks are only a doorbell: a company, deal or ticket creation pulls the next run forward, debounced by 60 seconds, but nima never uses the webhook payload. Records are always re-read through the API.
HubSpot accounts and ARR at stake
Signals from HubSpot are attributed to accounts in the index, up to 10 accounts per signal, and beacons roll up the ARR at stake across their evidence. GET /v1/accounts lists accounts with filters search, segmentKind, minArrUsd and ownerName. GET /v1/accounts/:id returns one account with its facts, latest signals, beacons and shipped specs. PUT /v1/signals/:signalId/accounts with { "accountIds": [...] } (up to 10 account ids) sets a signal's accounts by hand, and an empty list clears them. These routes need a signed-in session of any workspace member (owner, admin or member), and the account index is empty until a sync has run.
Delete HubSpot data and disconnect
DELETE /v1/hubspot/data removes the account index, contacts and signal-to-account links, and resets cursors and the Import state. It returns the counts removed. Signals already created are kept. The card's Delete account data button calls this route.
Disconnect with the Disconnect button on the HubSpot card in Integrations, which calls DELETE /v1/integrations/hubspot. nima revokes its HubSpot token, deletes the connection, and purges the account index, contacts and signal-to-account links. The signals themselves stay, with their source links to the HubSpot records, but nima can no longer re-read those originals. The On or Paused switch on the card is a label only and does not stop a sync.