> ## Documentation Index
> Fetch the complete documentation index at: https://docs.prisme.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# HubSpot

> Let Agent Factory agents read and write HubSpot CRM and Marketing data through MCP, with per-user OAuth resolved server-side

<img src="https://mintcdn.com/prismeai/wAgJDx_RXqITi8An/images/connectors/hubspot.png?fit=max&auto=format&n=wAgJDx_RXqITi8An&q=85&s=d2b528d415ff6468bdcf0bbe9bae6b12" alt="HubSpot" width="96" height="96" noZoom style={{ float: "left", marginRight: "1.25rem", marginBottom: "0.5rem" }} data-path="images/connectors/hubspot.png" />

The HubSpot connector exposes a HubSpot portal through the [HubSpot REST API](https://developers.hubspot.com/docs/api/overview) to **Agent Factory** agents (through MCP) and to Builder automations (through the `Hubspot.op:` App instructions) — covering the CRM (contacts, companies, deals, tickets, engagements, associations, pipelines, properties, owners, lists) and the Marketing layer (forms, marketing emails, workflows, files). The \~120 HubSpot operations are grouped into **18 entity tools**, each driven by an `action` argument. The MCP server runs in the **tenant app-instance context**: it resolves the installing workspace's own credentials and authorizes the calling agent against that workspace's allowlist. Authentication supports several modes:

* **Per-user OAuth2 — central client** (`oauthCentral`, recommended) — one HubSpot OAuth application is registered once by the platform maintainer; every end user signs in with their own HubSpot portal. Nothing to register per workspace: each one just installs the app and clicks *Connect*.
* **Per-user OAuth2 — tenant client** (`oauth`) — paste your own HubSpot OAuth client ID/secret in the connector config app. Each user signs in with their own account against your client (authorization-code + PKCE flow).
* **Private App token** (`accessToken`) — a caller-managed HubSpot **Private App access token (PAT)**, used as-is with no exchange. Best for service-account / non-interactive automations where a single identity is preferable.

<CardGroup cols={3}>
  <Card title="CRM Records" icon="address-book">
    Contacts, companies, deals and tickets with list/search/get/create/update/archive plus batch and merge operations
  </Card>

  <Card title="Engagements & Marketing" icon="diagram-project">
    Notes, tasks, calls, emails, meetings; pipelines, properties, owners, lists; forms, marketing emails, workflows and files
  </Card>

  <Card title="Per-user auth" icon="key">
    Per-user OAuth (central or tenant client) resolved server-side, or a caller-managed Private App token
  </Card>
</CardGroup>

## Who is this for?

This connector is used by three different roles. Jump to the section that matches yours — each one is self-contained.

<CardGroup cols={3}>
  <Card title="Agent builder" icon="robot">
    You build agents in **Agent Factory** and want them to use HubSpot. → *Agent builder* tab.
  </Card>

  <Card title="Platform admin" icon="shield-halved">
    You run the platform and set up the shared HubSpot OAuth client once for everyone. → *Platform admin setup* accordion below.
  </Card>

  <Card title="Workspace builder" icon="puzzle-piece">
    You write Builder automations (DSUL) that call HubSpot operations directly. → *Workspace builder* tab.
  </Card>
</CardGroup>

## Prerequisites

* A **HubSpot account** with access to the portal you want to expose.
* For the **OAuth modes** — a HubSpot OAuth application. **HubSpot's Developer Platform 2025.2 retired the legacy *Public Apps* UI**: the OAuth app is now declared as code in a HubSpot project and deployed via the HubSpot CLI (from a free **HubSpot developer account** at [developers.hubspot.com](https://developers.hubspot.com/), separate from any CRM account). The platform maintainer registers one app for the central mode; a workspace can also register its own for the tenant mode. Copy the **Client ID** and **Client Secret** from the deployed app's *Auth* tab.
* For the **Private App token mode** — a **Private App access token** created at *HubSpot Settings → Integrations → Private Apps* (`https://app.hubspot.com/private-apps/<portal-id>`), with the scopes covering the operations you intend to call.
* **OAuth scopes** — the connector requests the free-tier essentials by default (`oauth`, `crm.objects.contacts.*`, `crm.objects.companies.*`, `crm.objects.deals.*`, `crm.objects.owners.read`, `tickets`). Paid-tier features (lists, content, automation, forms, files) require their own scopes and a matching HubSpot subscription; every requested scope must be declared in the deployed app's `requiredScopes` / `optionalScopes`.
* Base URL: `https://api.hubapi.com` (single host).

<Accordion title="Platform admin (Governance) — one-time platform setup" icon="shield-halved">
  **Goal:** two one-time tasks — (1) configure the shared **central OAuth client** so every workspace lets its users sign in with their own HubSpot portal, and (2) optionally expose HubSpot as a reusable **capability** in AI Governance so agent builders can pick it without pasting endpoint URLs. Throughout, `<api-url>` is your environment's API URL (`https://api.studio.prisme.ai/v2` on production).

  ## 1. Configure the connector

  <Steps>
    <Step title="Register the OAuth application at HubSpot">
      Create a free **HubSpot developer account**, install the CLI (`npm install -g @hubspot/cli@latest`), then deploy the OAuth app project. The `hubspot` workspace ships ready-to-deploy projects under `workspaces/hubspot/project/` (one per environment); each `app-hsmeta.json` declares the redirect URI, required and optional scopes. Set the single Authorized redirect URI to the **core workspace** callback:

      ```text theme={null}
      <api-url>/workspaces/slug:hubspot/webhooks/oauthCallback
      ```

      (e.g. `https://api.studio.prisme.ai/v2/workspaces/slug:hubspot/webhooks/oauthCallback` on production). Deploy with `hs project upload && hs project deploy`, allowlist the portals (numeric Hub IDs) that may install the app, then copy the **Client ID** and **Client Secret** from the app's *Auth* tab (the secret is shown only once).
    </Step>

    <Step title="Enter the credentials through the configuration app">
      Open the central `hubspot` workspace and launch its **Configuration app** — `<studio>/apps/hubspot` (e.g. `https://studio.prisme.ai/apps/hubspot`). Loaded from the core workspace, the app shows the **maintainer view** (org owner / editor / admin only); paste the **Client ID** and **Client Secret** there and Save — the app stores them in the core workspace's secrets for you. Do not edit Studio's raw Secrets by hand. These credentials stay in the `hubspot` workspace and are never exposed to tenants or end users; token exchange is proxied through the core `centralTokenExchange` webhook so the client secret never leaves the core workspace.
    </Step>

    <Step title="Tell workspaces to use the central client">
      Each consuming workspace selects auth mode **`oauthCentral`** in the connector configuration app (no client id/secret to enter on their side). Their users then just click **Connect**.
    </Step>

    <Step title="(Optional) Publish to the Capabilities catalog">
      From that same maintainer view, the **Add to catalog** button publishes HubSpot to the org-wide **Capabilities catalog** in one click (org owner / admin only; it stays disabled until the central client is saved). Once published, every agent builder in the org can enable HubSpot from the catalog without pasting an endpoint — see *Option A* in the *Agent builder* tab.
    </Step>
  </Steps>

  ## 2. Declare the capability in AI Governance

  As an alternative to (or in addition to) the catalog button, you can declare HubSpot as a named **capability** in AI Governance. Agent builders then enable that capability on their agents instead of pasting a raw MCP endpoint.

  <Steps>
    <Step title="Open AI Governance > Capabilities">
      Create (or edit) the **HubSpot** capability.
    </Step>

    <Step title="Point it at the MCP endpoint">
      Set the capability's MCP server URL to the connector's **MCP Endpoint**, and set its **Scope** to:

      ```text theme={null}
      context_id,agent_id,user_id
      ```

      The `agent_id` in the scope is what lets the connector identify and authorize the calling agent.
    </Step>

    <Step title="Make it available to agent builders">
      Once created, the capability appears in the capability picker for agent builders in your organization, who enable it on their agents. Access to the catalog follows your organization's existing roles; there is no per-capability role grant.
    </Step>

    <Step title="Smoke-test">
      From an agent that has the capability, in a workspace configured for `oauthCentral`, trigger any tool (e.g. `contacts` with `action: "list"`). The user is prompted to connect once (HubSpot sign-in, pick a portal); subsequent calls reuse the stored token transparently and refresh it automatically.
    </Step>
  </Steps>

  <Warning>
    Declaring the capability makes the connector **available**; it does not by itself authorize a specific agent. This connector follows the **tenant-context model** — which agents may actually call it is gated per-workspace by the **authorized-agents allowlist** in the configuration app (see the *Workspace builder* tab). There is also **no OAuth auth-config JSON** to attach in Governance: connect / status / disconnect are handled by the connector's own webhooks, wired automatically.
  </Warning>

  <Note>
    **HubSpot scope handling has a strict OAuth-URL contract**: the connector lists required scopes in `scope=` and optional scopes in `optional_scope=`, and **every requested scope must already be declared in the HubSpot app's `requiredScopes` or `optionalScopes`**. Putting an optional-tier scope (Marketing Hub Pro+, CMS Hub, etc.) into `scope=` triggers a "domain mismatch" rejection. The shipped `app-hsmeta.json` keeps `requiredScopes` to the free-tier essentials; paid-tier scopes sit in `optionalScopes`. To add a scope, edit `app-hsmeta.json` for each environment and re-run `hs project upload && hs project deploy`.
  </Note>
</Accordion>

***

<Tabs>
  <Tab title="Agent builder (Agent Factory)">
    ## Agent builder

    **Goal:** let an agent you build in Agent Factory use HubSpot through MCP tools.

    <Note>
      Before an agent can call the connector, a *Workspace builder* must have installed and configured the HubSpot app in a workspace (see the *Workspace builder* tab), and — for the central OAuth mode — a *Platform admin* must have provisioned the shared OAuth client (see the *Platform admin setup* accordion above).
    </Note>

    This connector runs in the **tenant app-instance context**: your agent is authorized two ways at once — it is identified by the `agent_id` that Agent Factory injects through the capability *Scope*, and that agent must appear in the connector's **authorized-agents allowlist** (managed in the configuration app). The HubSpot access token itself is resolved server-side from the configured auth mode.

    There are two ways to wire it up. Pick based on how much isolation you need.

    ## Option A — Enable the shared capability from the catalog

    The fastest path: a *Platform admin* has already published a **HubSpot** capability to the Capabilities catalog (see the *Platform admin setup* accordion above — the **Add to catalog** button, or §2), so you just pick it from the catalog.

    <Steps>
      <Step title="Open your agent in Agent Factory">
        Open the agent you want to extend and go to its capabilities / tools.
      </Step>

      <Step title="Add the HubSpot capability">
        Browse the capability catalog, select **HubSpot**, and enable it. The MCP endpoint URL and the *Scope* (`context_id,agent_id,user_id`) are already wired by the admin — nothing to paste, and the shared instance accepts every agent, so there is no allowlist step on your side.
      </Step>

      <Step title="Connect a HubSpot account">
        On the first tool call, an unconnected user is prompted to sign in — Agent Factory surfaces a `connect_url` to HubSpot's authorization page, where the user picks the portal to authorize (from the allowlisted set) and reviews the requested scopes. After sign-in the per-user token is stored and reused on subsequent calls.
      </Step>
    </Steps>

    <Note>
      Convenient, but your agent runs against a **shared, platform-managed instance**: its HubSpot credentials are owned by someone else and the instance accepts every agent that is granted the capability. Prefer Option B for anything beyond quick experiments.
    </Note>

    ## Option B — Run it from your own workspace (recommended)

    For production agents, install the connector in **your own workspace** and point the agent at *that* workspace's MCP endpoint.

    <Warning>
      **Prefer this mode for security.** Because the MCP runs in *your* app-instance context, the HubSpot credentials, the per-user OAuth tokens and the authorized-agents allowlist are all **scoped to your workspace** — not shared platform-wide. You decide exactly which agents may call it and which HubSpot account / auth mode backs them, and a misconfiguration elsewhere can never expose your data. The shared catalog capability (Option A) is a broad surface many agents can reach; your own workspace is an isolated, least-privilege boundary.
    </Warning>

    <Steps>
      <Step title="Install and configure the connector in your workspace">
        Follow the *Workspace builder* tab: install **HubSpot** in your workspace, open its **Configuration app**, choose the auth mode and connect a HubSpot account (or paste a Private App token).
      </Step>

      <Step title="Allowlist your agent">
        In that workspace's config app, open **Authorized agents** and tick your agent (the **Install capability** button does this for you), or enable **Allow all agents**.
      </Step>

      <Step title="Add the MCP capability to your agent">
        In your agent, add a capability pointing at **your workspace's MCP Endpoint** URL, and set its **Scope** to:

        ```text theme={null}
        context_id,agent_id,user_id
        ```

        The `agent_id` is what lets the connector identify and authorize your agent — without it, every call is rejected with an explicit "agent could not be identified" message. This *Scope* is separate from the HubSpot OAuth scopes.
      </Step>

      <Step title="Connect a HubSpot account">
        On the first tool call, the user is prompted to sign in (or uses **Connect** in the config app). The per-user token is stored and reused; refresh is automatic.
      </Step>
    </Steps>

    ## Brief the agent in its system prompt

    Whichever option you pick, wiring the capability is not enough — the agent must know the connector exists and when to reach for it. Add a short paragraph to the agent's system prompt. Copy-pasteable starter:

    ```text theme={null}
    You have access to the HubSpot MCP server. Use it whenever the user asks about CRM data — contacts, companies, deals, tickets, engagements, pipelines or marketing assets (forms, emails, workflows). Each tool is an entity that takes an `action` argument. Always prefer `action: search` over `action: list` for filtered lookups. For batch work, use the `batch*` actions instead of looping individual calls. Always confirm with the user before destructive actions (archive, delete, batchArchive, unenrollContact, removeMembers).
    ```

    Refine the trigger keywords (resource names, business domains, typical user phrasings) so the agent reliably picks up the right intent in your context.

    <Note>
      **Legacy AI Knowledge agents** (no native MCP picker): add the connector under **Advanced > Tools > MCP** and paste the **MCP Endpoint** URL. The agent still has to be allowlisted in the config app and its identity propagated so the connector can read its `agent_id`.
    </Note>

    <Note>
      **Restricting to read-only (least privilege).** HubSpot tools cover both reads and writes (create/update contacts, companies, deals). The requested OAuth scopes **are** the grant. To allow only read access, set the **Scopes** field in the configuration app to the read-only set, dropping every `*.write` scope, e.g.:

      ```text theme={null}
      oauth crm.objects.contacts.read crm.objects.companies.read crm.objects.deals.read crm.objects.owners.read tickets
      ```

      With central OAuth (`oauthCentral`) you do **not** create your own HubSpot app — keep `oauthCentral` and enter the read scopes; your tenant scope overrides the platform default (the central app must list these read scopes). Write calls are then rejected by HubSpot with `403`. The scope is set at the workspace level (a workspace editor can widen it again).
    </Note>

    ## Available Tools

    The MCP server exposes 18 entity-level tools, each with an `action` argument to select the operation. Every tool accepts an `outputFormat` parameter (see *Output Formats*).

    ### `contacts` / `companies` / `deals`

    These three CRM tools share the same set of actions.

    | Action         | Description                                                                      |
    | -------------- | -------------------------------------------------------------------------------- |
    | `list`         | List records with pagination and property selection                              |
    | `get`          | Retrieve a single record by id (or by `idProperty`)                              |
    | `create`       | Create a record with properties and optional associations                        |
    | `update`       | Update a record by id                                                            |
    | `archive`      | Archive (soft-delete) a record                                                   |
    | `search`       | Full-text and structured search with filter groups, sorts and a `query` shortcut |
    | `merge`        | Merge two records into one (`primaryObjectId` ← `objectIdToMerge`)               |
    | `batchRead`    | Read up to 100 records at once                                                   |
    | `batchCreate`  | Create up to 100 records in one call                                             |
    | `batchUpdate`  | Update up to 100 records in one call                                             |
    | `batchArchive` | Archive up to 100 records in one call                                            |

    ### `tickets`

    Same actions as the CRM trio above, minus `merge`.

    ### `associations`

    | Action    | Description                                             |
    | --------- | ------------------------------------------------------- |
    | `list`    | List associations from a record to a target object type |
    | `create`  | Create an association between two records               |
    | `archive` | Remove an association between two records               |

    ### `notes` / `tasks` / `calls` / `engagementEmails` / `meetings`

    All five engagement tools share the same actions.

    | Action    | Description                                                       |
    | --------- | ----------------------------------------------------------------- |
    | `list`    | List engagements                                                  |
    | `get`     | Retrieve a single engagement                                      |
    | `create`  | Create an engagement, optionally with associations to CRM records |
    | `update`  | Update an engagement                                              |
    | `archive` | Archive an engagement                                             |

    ### `pipelines`

    | Action         | Description                       |
    | -------------- | --------------------------------- |
    | `list`         | List pipelines for an object type |
    | `get`          | Retrieve a pipeline               |
    | `create`       | Create a pipeline                 |
    | `update`       | Update a pipeline                 |
    | `archive`      | Archive a pipeline                |
    | `listStages`   | List the stages of a pipeline     |
    | `getStage`     | Retrieve a stage                  |
    | `createStage`  | Add a stage to a pipeline         |
    | `updateStage`  | Update a stage                    |
    | `archiveStage` | Archive a stage                   |

    ### `properties`

    | Action         | Description                             |
    | -------------- | --------------------------------------- |
    | `list`         | List properties for an object type      |
    | `get`          | Retrieve a property                     |
    | `create`       | Create a property                       |
    | `update`       | Update a property                       |
    | `archive`      | Archive a property                      |
    | `listGroups`   | List property groups for an object type |
    | `createGroup`  | Create a property group                 |
    | `updateGroup`  | Update a property group                 |
    | `archiveGroup` | Archive a property group                |

    ### `owners`

    | Action | Description                              |
    | ------ | ---------------------------------------- |
    | `list` | List owners (optionally filter by email) |
    | `get`  | Retrieve an owner by id                  |

    ### `lists`

    | Action           | Description                               |
    | ---------------- | ----------------------------------------- |
    | `create`         | Create a list                             |
    | `get`            | Retrieve a list (optionally with filters) |
    | `update`         | Rename a list                             |
    | `delete`         | Delete a list                             |
    | `search`         | Search lists by name and processing type  |
    | `addMembers`     | Add records to a static list              |
    | `removeMembers`  | Remove records from a static list         |
    | `getMemberships` | List the members of a list                |

    ### `forms`

    | Action    | Description                                    |
    | --------- | ---------------------------------------------- |
    | `list`    | List marketing forms                           |
    | `get`     | Retrieve a form                                |
    | `create`  | Create a form                                  |
    | `update`  | Update a form                                  |
    | `archive` | Archive a form                                 |
    | `submit`  | Submit values to a form on behalf of a visitor |

    ### `marketingEmails`

    | Action              | Description                                           |
    | ------------------- | ----------------------------------------------------- |
    | `list`              | List marketing emails                                 |
    | `get`               | Retrieve a marketing email                            |
    | `create`            | Create a marketing email                              |
    | `update`            | Update a marketing email                              |
    | `archive`           | Archive a marketing email                             |
    | `sendTransactional` | Send a transactional single-send email to a recipient |

    ### `workflows`

    | Action            | Description                                 |
    | ----------------- | ------------------------------------------- |
    | `list`            | List workflows                              |
    | `get`             | Retrieve a workflow                         |
    | `enrollContact`   | Enroll a contact in a workflow by email     |
    | `unenrollContact` | Unenroll a contact from a workflow by email |

    ### `files`

    | Action          | Description                            |
    | --------------- | -------------------------------------- |
    | `list`          | List files                             |
    | `get`           | Retrieve file metadata                 |
    | `getSignedUrl`  | Create a signed URL to download a file |
    | `archive`       | Archive a file                         |
    | `importFromUrl` | Import a file from a public URL        |
    | `listFolders`   | List folders                           |
    | `createFolder`  | Create a folder                        |
    | `archiveFolder` | Archive a folder                       |

    ## Output Formats

    Every tool accepts an `outputFormat` parameter that controls the MCP response shape:

    * **`verbose`** (default) — human-readable text for LLM consumption
    * **`structured`** — machine-readable JSON in `structuredContent`
    * **`both`** — both text and structured content

    ## Tool Details

    ### `contacts` (action: `search`)

    ```json theme={null}
    {
      "name": "contacts",
      "arguments": {
        "action": "search",
        "filterGroups": [
          {
            "filters": [
              { "propertyName": "email", "operator": "EQ", "value": "alice@acme.com" }
            ]
          }
        ],
        "properties": ["email", "firstname", "lastname", "lifecyclestage"],
        "limit": 1
      }
    }
    ```

    | Parameter      | Required | Description                                                                                                    |
    | -------------- | -------- | -------------------------------------------------------------------------------------------------------------- |
    | `action`       | Yes      | Must be `search`                                                                                               |
    | `filterGroups` | No       | Array of `{ filters: [{ propertyName, operator, value }] }`. Filters inside a group are AND'd; groups are OR'd |
    | `sorts`        | No       | Array of `{ propertyName, direction }`                                                                         |
    | `query`        | No       | Free-text query alternative to `filterGroups`                                                                  |
    | `properties`   | No       | Properties to include in the response                                                                          |
    | `limit`        | No       | Page size (max 100)                                                                                            |
    | `after`        | No       | Cursor from a previous response (`paging.next.after`)                                                          |

    ### `deals` (action: `create`)

    ```json theme={null}
    {
      "name": "deals",
      "arguments": {
        "action": "create",
        "properties": {
          "dealname": "Acme — annual contract",
          "amount": "120000",
          "dealstage": "appointmentscheduled",
          "pipeline": "default",
          "closedate": "2026-09-30"
        },
        "associations": [
          {
            "to": { "id": "11223344" },
            "types": [
              { "associationCategory": "HUBSPOT_DEFINED", "associationTypeId": 5 }
            ]
          }
        ]
      }
    }
    ```

    | Parameter      | Required | Description                                                                                             |
    | -------------- | -------- | ------------------------------------------------------------------------------------------------------- |
    | `action`       | Yes      | Must be `create`                                                                                        |
    | `properties`   | Yes      | Map of HubSpot deal properties                                                                          |
    | `associations` | No       | Optional list of association targets (`{ to.id, types: [{ associationCategory, associationTypeId }] }`) |

    ### `lists` (action: `addMembers`)

    ```json theme={null}
    {
      "name": "lists",
      "arguments": {
        "action": "addMembers",
        "listId": "12345"
      }
    }
    ```

    | Parameter | Required | Description                                             |
    | --------- | -------- | ------------------------------------------------------- |
    | `action`  | Yes      | Must be `addMembers`                                    |
    | `listId`  | Yes      | Target list ID (only valid for static / `MANUAL` lists) |

    ### `marketingEmails` (action: `sendTransactional`)

    ```json theme={null}
    {
      "name": "marketingEmails",
      "arguments": {
        "action": "sendTransactional",
        "emailId": "67890",
        "message": {
          "to": "alice@acme.com",
          "from": "noreply@acme.com"
        },
        "contactProperties": { "firstname": "Alice" },
        "customProperties": { "orderId": "ORD-12345" }
      }
    }
    ```

    | Parameter           | Required | Description                                          |
    | ------------------- | -------- | ---------------------------------------------------- |
    | `action`            | Yes      | Must be `sendTransactional`                          |
    | `emailId`           | Yes      | The single-send email template ID                    |
    | `message`           | Yes      | `{ to, from, cc, bcc, replyTo, sendId }`             |
    | `contactProperties` | No       | Properties to set on the contact for personalization |
    | `customProperties`  | No       | Custom merge tags resolved inside the email body     |

    ### `workflows` (action: `enrollContact`)

    ```json theme={null}
    {
      "name": "workflows",
      "arguments": {
        "action": "enrollContact",
        "workflowId": "98765",
        "email": "alice@acme.com"
      }
    }
    ```

    | Parameter    | Required | Description                    |
    | ------------ | -------- | ------------------------------ |
    | `action`     | Yes      | Must be `enrollContact`        |
    | `workflowId` | Yes      | Target workflow                |
    | `email`      | Yes      | Email of the contact to enroll |
  </Tab>

  <Tab title="Workspace builder (DSUL)">
    ## Workspace builder

    **Goal:** install the connector in a workspace, configure authentication and the agent allowlist, and call HubSpot operations from your automations.

    ## Installation

    1. Go to **Apps** in your workspace
    2. Search for **HubSpot** and install it
    3. Open the **Configuration app** (the link auto-populated on install) to choose the auth mode, provide credentials, connect a HubSpot account, and allow the agents that may call the connector

    ## Configuration

    | Field                 | Description                                                                                                                                                                       |
    | --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | **Configuration app** | Auto-populated on install — open this link to configure authentication (mode + credentials), connect a HubSpot account (OAuth modes), and manage the authorized-agents allowlist. |

    The configuration app drives everything; the app instance itself has no per-field credential form. From it you pick one of:

    | Auth mode      | What you provide                                                               | Best for                                                                                                                                |
    | -------------- | ------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------- |
    | `oauthCentral` | Nothing — just click **Connect**                                               | Workspaces on a platform where the maintainer set up the central HubSpot client; human-facing agents where each user acts as themselves |
    | `oauth`        | Your own HubSpot **Client ID + Secret** (redirect URI shown in the config app) | You manage your own HubSpot OAuth app                                                                                                   |
    | `accessToken`  | A HubSpot **Private App access token (PAT)**                                   | Service-account / non-interactive automations where one identity is preferable                                                          |

    <Note>
      Credentials are stored in the workspace's Secrets (`hubspotAuth`) and resolved server-side. The agent allowlist (**Authorized agents**, secret `hubspotAuthorizedAgents`) gates which Agent Factory agents may call the MCP endpoint — see the *Agent builder* tab. Builder automations calling `Hubspot.op:` instructions resolve the same workspace credentials.
    </Note>

    ## Available Instructions

    Every instruction resolves credentials from the workspace configuration. CRM list operations accept `limit` (default 100) and `after` for cursor pagination, plus `properties` to request specific fields.

    ### Contacts

    | Instruction                                                                                  | Description                                                                 | Returns                                     |
    | -------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- | ------------------------------------------- |
    | `listContacts`                                                                               | List contacts; paginate with `limit`/`after`, pick fields with `properties` | `{ results: [{ id, properties }], paging }` |
    | `getContact`                                                                                 | Get a contact by `contactId` (or by `idProperty`)                           | `{ id, properties, associations }`          |
    | `createContact`                                                                              | Create a contact from `properties`, optional `associations`                 | `{ id, properties }`                        |
    | `updateContact`                                                                              | Update a contact by `contactId` with `properties`                           | `{ id, properties }`                        |
    | `archiveContact`                                                                             | Archive a contact by `contactId`                                            | Empty (HTTP 204)                            |
    | `searchContacts`                                                                             | Search with `filterGroups`/`sorts`/`query`, select `properties`             | `{ total, results, paging }`                |
    | `mergeContacts`                                                                              | Merge `objectIdToMerge` into `primaryObjectId`                              | `{ id, properties }`                        |
    | `batchReadContacts` / `batchCreateContacts` / `batchUpdateContacts` / `batchArchiveContacts` | Batch ops on up to 100 `inputs`                                             | `{ status, results }`                       |

    ### Companies

    Same shape as contacts: `listCompanies`, `getCompany`, `createCompany`, `updateCompany`, `archiveCompany`, `searchCompanies`, `mergeCompanies`, `batchReadCompanies`, `batchCreateCompanies`, `batchUpdateCompanies`, `batchArchiveCompanies`.

    ### Deals

    Same shape as contacts: `listDeals`, `getDeal`, `createDeal`, `updateDeal`, `archiveDeal`, `searchDeals`, `mergeDeals`, `batchReadDeals`, `batchCreateDeals`, `batchUpdateDeals`, `batchArchiveDeals`.

    ### Tickets

    Same shape as the CRM trio, minus merge: `listTickets`, `getTicket`, `createTicket`, `updateTicket`, `archiveTicket`, `searchTickets`, `batchReadTickets`, `batchCreateTickets`, `batchUpdateTickets`, `batchArchiveTickets`.

    ### Associations

    | Instruction          | Description                                                              | Returns                                                   |
    | -------------------- | ------------------------------------------------------------------------ | --------------------------------------------------------- |
    | `listAssociations`   | List associations from `fromObjectType`/`fromObjectId` to `toObjectType` | `{ results: [{ toObjectId, associationTypes }], paging }` |
    | `createAssociation`  | Default association between `fromObject` and `toObject`                  | `{ fromObjectTypeId, toObjectId, labels }`                |
    | `archiveAssociation` | Remove an association between two records                                | Empty (HTTP 204)                                          |

    ### Engagements (Notes / Tasks / Calls / Emails / Meetings)

    | Instruction                                                                       | Description                                                      | Returns                            |
    | --------------------------------------------------------------------------------- | ---------------------------------------------------------------- | ---------------------------------- |
    | `listNotes` / `listTasks` / `listCalls` / `listEmails` / `listMeetings`           | List engagements; `limit`, `after`, `properties`, `associations` | `{ results, paging }`              |
    | `getNote` / `getTask` / `getCall` / `getEmail` / `getMeeting`                     | Get an engagement by its id                                      | `{ id, properties, associations }` |
    | `createNote` / `createTask` / `createCall` / `createEmail` / `createMeeting`      | Create from `properties`, optional `associations`                | `{ id, properties }`               |
    | `updateNote` / `updateTask` / `updateCall` / `updateEmail` / `updateMeeting`      | Update an engagement by id                                       | `{ id, properties }`               |
    | `archiveNote` / `archiveTask` / `archiveCall` / `archiveEmail` / `archiveMeeting` | Archive an engagement by id                                      | Empty (HTTP 204)                   |

    ### Pipelines

    | Instruction                                                            | Description                                            | Returns                                   |
    | ---------------------------------------------------------------------- | ------------------------------------------------------ | ----------------------------------------- |
    | `listPipelines`                                                        | List pipelines for `objectType` (`deals` or `tickets`) | `{ results: [{ id, label, stages }] }`    |
    | `getPipeline`                                                          | Get a pipeline by `objectType` + `pipelineId`          | `{ id, label, stages }`                   |
    | `createPipeline`                                                       | Create a pipeline (`label`, `stages`, `displayOrder`)  | `{ id, label, stages }`                   |
    | `updatePipeline`                                                       | Update a pipeline by `pipelineId`                      | `{ id, label, stages }`                   |
    | `archivePipeline`                                                      | Archive a pipeline by `pipelineId`                     | Empty (HTTP 204)                          |
    | `listPipelineStages` / `getPipelineStage`                              | List / get pipeline stages                             | `{ results }` / `{ id, label, metadata }` |
    | `createPipelineStage` / `updatePipelineStage` / `archivePipelineStage` | Manage a stage by `stageId`                            | `{ id, label, metadata }` / 204           |

    ### Properties

    `listProperties`, `getProperty`, `createProperty`, `updateProperty`, `archiveProperty`, `listPropertyGroups`, `createPropertyGroup`, `updatePropertyGroup`, `archivePropertyGroup` — each scoped by `objectType`; returns property / property-group definitions.

    ### Owners

    | Instruction  | Description                                       | Returns                                                     |
    | ------------ | ------------------------------------------------- | ----------------------------------------------------------- |
    | `listOwners` | List owners; optional `email` filter              | `{ results: [{ id, email, firstName, lastName }], paging }` |
    | `getOwner`   | Get an owner by `ownerId` (match on `idProperty`) | `{ id, email, firstName, lastName }`                        |

    ### Lists

    | Instruction                            | Description                                                              | Returns                                         |
    | -------------------------------------- | ------------------------------------------------------------------------ | ----------------------------------------------- |
    | `createList`                           | Create a list (`name`, `objectTypeId`, `processingType`, `filterBranch`) | `{ list: { listId, name } }`                    |
    | `getList`                              | Get a list by `listId`; `includeFilters` for the filter tree             | `{ list: { listId, name, filtersUpdatedAt } }`  |
    | `updateList`                           | Rename a list (`listId`, `listName`)                                     | `{ list }`                                      |
    | `deleteList`                           | Delete a list by `listId`                                                | Empty (HTTP 204)                                |
    | `searchLists`                          | Search by `query`, paginate with `count`/`offset`                        | `{ lists, total, offset }`                      |
    | `addListMembers` / `removeListMembers` | Add / remove members of a static list by `listId`                        | `{ recordsIdsAdded }` / `{ recordsIdsRemoved }` |
    | `getListMemberships`                   | List the members of a list by `listId`                                   | `{ results, paging }`                           |

    ### Forms

    | Instruction   | Description                                                        | Returns                     |
    | ------------- | ------------------------------------------------------------------ | --------------------------- |
    | `listForms`   | List marketing forms; `formTypes` filter                           | `{ results, paging }`       |
    | `getForm`     | Get a form by `formId`                                             | `{ id, name, fieldGroups }` |
    | `createForm`  | Create a form (`name`, `formType`, `fieldGroups`, `configuration`) | `{ id, name }`              |
    | `updateForm`  | Update a form by `formId`                                          | `{ id, name }`              |
    | `archiveForm` | Archive a form by `formId`                                         | Empty (HTTP 204)            |
    | `submitForm`  | Submit `fields` to a form (`portalId`, `formGuid`, `context`)      | `{ inlineMessage }`         |

    ### Marketing Emails

    | Instruction              | Description                                                                              | Returns                        |
    | ------------------------ | ---------------------------------------------------------------------------------------- | ------------------------------ |
    | `listMarketingEmails`    | List marketing emails; `type`, `isPublished` filters                                     | `{ results, paging }`          |
    | `getMarketingEmail`      | Get a marketing email by `emailId`; `includeStats`                                       | `{ id, name, subject, stats }` |
    | `createMarketingEmail`   | Create a marketing email (`name`, `subject`, `fromName`, `content`)                      | `{ id, name }`                 |
    | `updateMarketingEmail`   | Update a marketing email by `emailId`                                                    | `{ id, name }`                 |
    | `archiveMarketingEmail`  | Archive a marketing email by `emailId`                                                   | Empty (HTTP 204)               |
    | `sendTransactionalEmail` | Send a single-send email (`emailId`, `message`, `contactProperties`, `customProperties`) | `{ status, sendResult }`       |

    ### Workflows

    | Instruction                   | Description                                | Returns                 |
    | ----------------------------- | ------------------------------------------ | ----------------------- |
    | `listWorkflows`               | List workflows; `limit`, `offset`          | `{ workflows }`         |
    | `getWorkflow`                 | Get a workflow by `workflowId`             | `{ id, name, enabled }` |
    | `enrollContactInWorkflow`     | Enroll a contact (`workflowId`, `email`)   | Empty (HTTP 204)        |
    | `unenrollContactFromWorkflow` | Unenroll a contact (`workflowId`, `email`) | Empty (HTTP 204)        |

    ### Files

    | Instruction                                      | Description                                                                            | Returns                              |
    | ------------------------------------------------ | -------------------------------------------------------------------------------------- | ------------------------------------ |
    | `listFiles`                                      | List files; filter by folder, `name`, `type`, `extension`                              | `{ results, paging }`                |
    | `getFile`                                        | Get file metadata by `fileId`                                                          | `{ id, name, url, size }`            |
    | `getFileSignedUrl`                               | Temporary signed URL for a file (`expirationSeconds`, `size`, `upscale`)               | `{ url, expiresAt }`                 |
    | `archiveFile`                                    | Archive a file by `fileId`                                                             | Empty (HTTP 204)                     |
    | `importFileFromUrl`                              | Async import from a public `url` (`access`, `folderId`, `duplicateValidationStrategy`) | `{ id, requestId }`                  |
    | `listFolders` / `createFolder` / `archiveFolder` | Manage folders                                                                         | `{ results }` / `{ id, name }` / 204 |

    <Note>
      `Returns` shows the shape of the operation output (the underlying HubSpot API resource).
    </Note>

    ## DSUL Examples

    ### Look up a contact by email

    ```yaml theme={null}
    - Hubspot.searchContacts:
        filterGroups:
          - filters:
              - propertyName: email
                operator: EQ
                value: '{{lead.email}}'
        properties:
          - email
          - firstname
          - lastname
          - lifecyclestage
        limit: 1
        output: matched
    ```

    ### Create a deal and associate it to a company

    ```yaml theme={null}
    - Hubspot.createDeal:
        properties:
          dealname: '{{opportunity.name}}'
          amount: '{{opportunity.amount}}'
          dealstage: 'appointmentscheduled'
          pipeline: 'default'
          closedate: '{{opportunity.closeDate}}'
        associations:
          - to:
              id: '{{companyId}}'
            types:
              - associationCategory: HUBSPOT_DEFINED
                associationTypeId: 5
        output: created
    ```

    ### Add a contact to a static list

    ```yaml theme={null}
    - Hubspot.addListMembers:
        listId: '{{leadsListId}}'
        recordIds:
          - '{{contactId}}'
    ```

    ### Send a transactional email

    ```yaml theme={null}
    - Hubspot.sendTransactionalEmail:
        emailId: '{{templateId}}'
        message:
          to: '{{lead.email}}'
          from: 'noreply@acme.com'
        contactProperties:
          firstname: '{{lead.firstName}}'
        customProperties:
          orderId: '{{order.id}}'
          orderTotal: '{{order.total}}'
    ```

    ### Bulk-import contacts

    ```yaml theme={null}
    - Hubspot.batchCreateContacts:
        inputs:
          - properties:
              email: 'alice@acme.com'
              firstname: 'Alice'
              lastname: 'Doe'
          - properties:
              email: 'bob@acme.com'
              firstname: 'Bob'
              lastname: 'Smith'
        output: batchResult
    ```
  </Tab>
</Tabs>

***

## Error Handling

| HTTP Status           | Meaning      | Typical Cause                                                                                                  |
| --------------------- | ------------ | -------------------------------------------------------------------------------------------------------------- |
| `400`                 | Bad Request  | Invalid property value, malformed `filterGroups`, missing required field                                       |
| `401`                 | Unauthorized | Missing / invalid PAT or OAuth token, revoked refresh token                                                    |
| `403`                 | Forbidden    | The token lacks the required scope, or the portal does not have the feature enabled (Marketing Hub Pro+, etc.) |
| `404`                 | Not Found    | Record / list / workflow / form does not exist (or was archived)                                               |
| `409`                 | Conflict     | Unique-property collision (e.g. a contact with the same email already exists)                                  |
| `429`                 | Rate Limited | Per-portal quota exceeded — back off using the `X-HubSpot-RateLimit-Secondly-Remaining` headers                |
| `500` / `502` / `503` | Server Error | Transient HubSpot API error — retry with exponential backoff                                                   |

### Common Issues

**"This agent is not authorized to use this connector"** — The calling agent is not in the allowlist. Open the configuration app → **Authorized agents** → tick this agent (id is shown in the error) or enable **Allow all agents**, and Save. The **Install capability** button does this for you.

**"The calling agent could not be identified"** — The MCP capability *Scope* does not declare `agent_id`, so Agent Factory never injects the agent identity. Set the Scope to `context_id,agent_id,user_id` on the capability (this is separate from the HubSpot OAuth scopes), then allow the agent in the config app.

**"HubSpot is not connected for this user"** — No per-user OAuth token is stored for the caller. Open the configuration app (OAuth mode) and click **Connect**, or use the agent's connect flow.

**"HubSpot token refresh failed … must reconnect"** — The stored refresh token was rejected by HubSpot (revoked, or expired after \~6 months idle). The connection is dropped automatically; the user must reconnect from the config app.

**"HubSpot OAuth is not configured"** — Neither a tenant OAuth client nor the central platform client is available. Set the OAuth client ID/secret in the config app (tenant mode), or ask the platform maintainer to provision the central OAuth client from the core workspace's config app.

**`invalid_scope` / "domain mismatch" during the OAuth dance** — The OAuth URL requests a scope the deployed HubSpot app does not declare. Every requested scope must appear in `requiredScopes` or `optionalScopes` of `app-hsmeta.json`; re-run `hs project upload && hs project deploy` after editing.

**`OBJECT_ALREADY_EXISTS` on contact create** — A contact with the same email already exists. Either `update` the existing contact (use `email` as `idProperty`) or query first with `search`.

**`Lists API: cannot add members to a dynamic list`** — Only static / `MANUAL` lists accept `addMembers` and `removeMembers`. Dynamic lists are driven by their filter criteria.

**No token revocation** — HubSpot exposes no RFC 7009 revocation endpoint, so disconnecting deletes the platform-stored tokens rather than revoking them at HubSpot — refresh tokens may stay valid until they naturally expire (typically 6 months idle).

## External Resources

<CardGroup cols={2}>
  <Card title="HubSpot Developer Docs" icon="book" href="https://developers.hubspot.com/docs/api/overview">
    Official HubSpot REST API reference
  </Card>

  <Card title="HubSpot OAuth scopes" icon="key" href="https://developers.hubspot.com/docs/apps/developer-platform/build-apps/authentication/scopes">
    Required vs optional scopes, scope-tier matrix, scope group reference
  </Card>

  <Card title="Tool Agents" icon="robot" href="/products/agent-factory/capabilities">
    Learn how Agent Factory agents consume MCP tools in Prisme.ai.
  </Card>
</CardGroup>
