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

# API reference: HubSpot CMS

> All 23 tools in the HubSpot CMS connector: 14 read tools covering pages, blog, HubDB and traffic analytics; 9 guardrailed write tools for drafts, publishing and HubDB row management. WritePreview dry-run semantics and confirm:true contract documented.

HubSpot CMS is a **read + guardrailed write** connector, called via `tools/call` over
`POST /hubspot/mcp`. The connector is connected through OAuth (HubSpot login; see
[Forbind HubSpot CMS](/connect/forbind-hubspot)).

<Info>
  Endpoint: `https://mcp.consile.ai/hubspot/mcp`. HubSpot CMS is its own named
  connector with only its own tools.
</Info>

<Warning>
  The connected HubSpot portal must be on **CMS Hub, Content Hub, or Marketing Hub
  Professional/Enterprise**. The OAuth scopes `content`, `hubdb`, and
  `business-intelligence` are gated at consent: a Starter or Free portal cannot grant
  them and the connection attempt fails at the HubSpot consent screen. See the
  [tier requirement note](#portal-tier-requirement) below.
</Warning>

***

## Write safety: guardrail model

The nine write tools split into two tiers based on reversibility:

1. **Draft operations run freely.** Creating a page, editing a blog post draft, or
   upserting a HubDB row all target the unpublished draft buffer. Nothing reaches the
   live site and the change can be discarded. No confirmation required.
2. **Publishing and destructive actions require `confirm: true`.** These are: pushing
   a page draft live (`publish_page`), publishing a blog post draft (`publish_blog_post`),
   publishing a HubDB table draft to live pages (`publish_hubdb_table`), and discarding
   a HubDB draft (`reset_hubdb_draft`). Without `confirm: true`, the tool returns a
   `WritePreview` dry-run and calls **no** HubSpot API.

This is a designed-in safety default (connector-local application logic), not an
unbreakable platform gate. See
[Handlinger & guardrails](/essentials/handlinger-og-guardrails) for the full
cross-connector policy.

### WritePreview shape

When a write action requires confirmation but `confirm` is omitted or `false`, the tool
returns this shape instead of calling HubSpot:

```json theme={null}
{
  "preview": {
    "action": "publish_page",
    "summary": "Publish site page 12345678 draft to the LIVE site.",
    "request": {
      "method": "POST",
      "path": "/cms/v3/pages/site-pages/12345678/draft/push-live",
      "body": {}
    },
    "confirmHint": "Re-run the same call with confirm: true to apply this change."
  }
}
```

To apply, re-run the identical call with `confirm: true` added.

### Draft-first default on create

New pages and blog posts are always created with `state: "DRAFT"`. The connector
enforces this regardless of what the caller sends, so nothing is accidentally published
on first create.

***

## Portal tier requirement

The OAuth scopes this connector requests (`content`, `hubdb`, `business-intelligence`)
require the connected HubSpot portal to be on one of:

* CMS Hub or Content Hub
* Marketing Hub Professional or Enterprise

A Starter or Free portal cannot grant these scopes. The connection attempt fails at
the HubSpot OAuth consent screen with a scope-permission error. If your customer hits
that error, they need to upgrade their HubSpot subscription before connecting.

***

## Authentication

HubSpot CMS uses the standard OAuth authorization-code flow with refresh token. Each
tenant authorizes Consile's HubSpot app and their `refresh_token` is vaulted under
`(tenantId, "hubspot")`. Access tokens expire after roughly 30 minutes and are
renewed silently by the platform's refreshing vault. The AI never sees the token.

**OAuth scopes:** `content`, `hubdb`, `business-intelligence`, `oauth` (the last is
auto-injected by HubSpot on every grant and is not a functional scope).

**Revoking access.** There is no upstream revoke in the portal in v1. To pull access
back from HubSpot, uninstall the Consile app in HubSpot's connected apps settings.
Disconnecting in the Consile portal deletes the vaulted token immediately.

***

## Shared parameters

### Paging

List tools use cursor-based paging. Pass `after` from `paging.next.after` in the
previous response to retrieve the next page.

<ParamField path="limit" type="integer" default="20">
  Max items to return in this page (1-100).
</ParamField>

<ParamField path="after" type="string">
  Cursor from a previous response's `paging.next.after`.
</ParamField>

### Page type

<ParamField path="type" type="string" default="site">
  Which page tree: `"site"` (website pages) or `"landing"` (landing pages).
</ParamField>

### HubDB table reference

<ParamField path="tableIdOrName" type="string" required>
  HubDB table numeric id (preferred) or unique table name.
</ParamField>

***

## Read tools: pages (4 tools)

### list\_pages

List website or landing pages with paging. Pass `archived: true` to list archived
pages instead of live ones.

<ParamField path="type" type="string" default="site" />

<ParamField path="limit" type="integer" default="20" />

<ParamField path="after" type="string" />

<ParamField path="archived" type="boolean">
  List archived pages instead of live ones.
</ParamField>

**Returns:** `{ results, paging }` where each result includes `id`, `name`, `slug`,
`state`, `htmlTitle`, `metaDescription`, `publishDate`, `archivedAt`, `createdAt`,
`updatedAt`.

***

### get\_page

Retrieve a single site or landing page (the live/published version) by id.

<ParamField path="type" type="string" default="site" />

<ParamField path="objectId" type="string" required>
  The page id.
</ParamField>

**Returns:** Full page object.

***

### get\_page\_draft

Read the unpublished draft buffer of a page to inspect pending edits before
publishing.

<ParamField path="type" type="string" default="site" />

<ParamField path="objectId" type="string" required>
  The page id.
</ParamField>

**Returns:** Draft page object (same shape as `get_page` but reflects staged edits).

***

### list\_page\_revisions

List prior published revisions of a page for review or rollback planning.

<ParamField path="type" type="string" default="site" />

<ParamField path="objectId" type="string" required>
  The page id.
</ParamField>

<ParamField path="limit" type="integer" default="20" />

<ParamField path="after" type="string" />

**Returns:** `{ results, paging }` where each revision includes `id`, `createdAt`,
`createdBy`.

***

## Read tools: blog (4 tools)

### list\_blog\_posts

List blog posts with paging. Pass `archived: true` to list archived posts.

<ParamField path="limit" type="integer" default="20" />

<ParamField path="after" type="string" />

<ParamField path="archived" type="boolean">
  List archived posts instead of live ones.
</ParamField>

**Returns:** `{ results, paging }` where each result includes `id`, `name`, `slug`,
`state`, `contentGroupId`, `htmlTitle`, `metaDescription`, `publishDate`, `tagIds`,
`authorName`, `createdAt`, `updatedAt`.

***

### get\_blog\_post

Get a single blog post by id. Set `draft: true` to read the draft buffer instead of
the live version.

<ParamField path="objectId" type="string" required>
  The blog post id.
</ParamField>

<ParamField path="draft" type="boolean">
  Read the draft buffer instead of the live version.
</ParamField>

**Returns:** Full blog post object including `postBody` (HTML content).

***

### list\_blog\_tags

List blog tags with paging.

<ParamField path="limit" type="integer" default="20" />

<ParamField path="after" type="string" />

**Returns:** `{ results, paging }` where each tag includes `id`, `name`, `slug`,
`createdAt`, `updatedAt`.

***

### list\_blog\_authors

List blog authors with paging. Authors are referenced by posts; this is a
read-through of the author registry.

<ParamField path="limit" type="integer" default="20" />

<ParamField path="after" type="string" />

**Returns:** `{ results, paging }` where each author includes `id`, `displayName`,
`email`, `slug`, `avatar`.

***

## Read tools: HubDB (5 tools)

### list\_hubdb\_tables

List the account's HubDB tables with id, name, label, column metadata, row count,
and publish state.

<ParamField path="limit" type="integer" default="20" />

<ParamField path="after" type="string" />

**Returns:** `{ results, paging }` where each table includes `id`, `name`, `label`,
`columns` (array with type, name, label, options), `rowCount`, `publishedAt`.

***

### get\_hubdb\_table

Get a single HubDB table's full definition including column schema: types, options,
and `foreignTableId`/`foreignColumnId` for `FOREIGN_ID` columns. Call this before
writing rows to understand the expected value types.

<ParamField path="tableIdOrName" type="string" required />

**Returns:** Full table definition including the `columns` array.

***

### list\_hubdb\_rows

List rows of a published HubDB table with paging. Returns each row's cell values
per column.

<ParamField path="tableIdOrName" type="string" required />

<ParamField path="limit" type="integer" default="20" />

<ParamField path="after" type="string" />

**Returns:** `{ results, paging }` where each row includes `id`, `values` (map of
column name to cell value).

***

### get\_hubdb\_row

Get a single published HubDB row by rowId.

<ParamField path="tableIdOrName" type="string" required />

<ParamField path="rowId" type="string" required>
  The row id.
</ParamField>

**Returns:** `{ id, values }`.

***

### export\_hubdb\_table

Export a HubDB table's data as a CSV or Excel file. Returns the raw file content.

<ParamField path="tableIdOrName" type="string" required />

<ParamField path="format" type="string" default="CSV">
  Export format: `"CSV"` or `"EXCEL"`.
</ParamField>

**Returns:** `{ tableIdOrName, format, data }` where `data` is the raw file content
as a string.

***

## Read tools: analytics (1 tool)

### get\_traffic\_analytics

Read traffic and page-view analytics over a date range. Optionally broken down by
content, source, geolocation, or UTM campaign.

<Note>
  Requires Marketing Hub Professional/Enterprise on the connected portal. The
  `business-intelligence` scope gates this endpoint.
</Note>

<ParamField path="breakdown" type="string" default="totals">
  How to break the traffic down. One of: `totals`, `sessions`, `sources`,
  `geolocation`, `pages`, `landing-pages`, `utm-campaigns`.
</ParamField>

<ParamField path="timePeriod" type="string" default="total">
  Aggregation period. One of: `total`, `daily`, `weekly`, `monthly`,
  `summarize/daily`, `summarize/weekly`, `summarize/monthly`.
</ParamField>

<ParamField path="start" type="string" required>
  Start date in `YYYYMMDD` format (inclusive).
</ParamField>

<ParamField path="end" type="string" required>
  End date in `YYYYMMDD` format (inclusive).
</ParamField>

<ParamField path="limit" type="integer">
  Max breakdown rows to return (up to 1000).
</ParamField>

**Returns:** HubSpot analytics report for the requested breakdown and period.

***

## Write tools (9 guardrailed)

<Warning>
  Write tools that push content to the live site or discard draft work require
  `confirm: true`. Without it the tool returns a [WritePreview](#writepreview-shape)
  and calls no HubSpot API. Draft-only operations (create, edit) run freely. See
  [Handlinger & guardrails](/essentials/handlinger-og-guardrails).
</Warning>

### create\_page\_draft

Create a new site or landing page as an unpublished draft. State is forced to `DRAFT`
regardless of what you pass; nothing goes live on create. Publish later with
`publish_page`.

**Does not require confirmation** (reversible draft operation).

<ParamField path="type" type="string" default="site" />

<ParamField path="name" type="string" required>
  Internal page name.
</ParamField>

<ParamField path="templatePath" type="string">
  Path to the template/theme file the page uses.
</ParamField>

<ParamField path="slug" type="string">
  URL slug (path) for the page.
</ParamField>

<ParamField path="htmlTitle" type="string">
  SEO `<title>` for the page.
</ParamField>

<ParamField path="metaDescription" type="string">
  SEO meta description.
</ParamField>

**Returns:** The created draft page object.

***

### update\_page\_draft

Edit a page's draft buffer (name, slug, SEO title, SEO description) without
touching the live page. Publish the change later with `publish_page`.

**Does not require confirmation** (draft buffer only, live page untouched).

<ParamField path="type" type="string" default="site" />

<ParamField path="objectId" type="string" required>
  The page id.
</ParamField>

<ParamField path="name" type="string" />

<ParamField path="slug" type="string" />

<ParamField path="htmlTitle" type="string" />

<ParamField path="metaDescription" type="string" />

**Returns:** The updated draft page object.

***

### publish\_page

Push a page's unpublished draft changes live to the public website. This is a
push-live of the draft: it updates an already-published page, not a first-time
publish of a brand-new page.

**Requires `confirm: true`.** Without it, returns a WritePreview dry-run describing
exactly what would be published.

<ParamField path="type" type="string" default="site" />

<ParamField path="objectId" type="string" required>
  The page id.
</ParamField>

<ParamField path="confirm" type="boolean">
  Set `true` to apply. Omit or `false` to receive a WritePreview.
</ParamField>

**Returns:** HubSpot's push-live response on success; `WritePreview` if not confirmed.

***

### create\_blog\_post\_draft

Create a new blog post as an unpublished draft. State is forced to `DRAFT`. Requires
the target blog's `contentGroupId` (read it from an existing post via `list_blog_posts`
or find it in the HubSpot UI). Publish later with `publish_blog_post`.

**Does not require confirmation** (reversible draft operation).

<ParamField path="contentGroupId" type="string" required>
  The blog (content group) id this post belongs to.
</ParamField>

<ParamField path="name" type="string" required>
  Post title / internal name.
</ParamField>

<ParamField path="slug" type="string">
  URL slug for the post.
</ParamField>

<ParamField path="postBody" type="string">
  Post body HTML.
</ParamField>

<ParamField path="htmlTitle" type="string">
  SEO `<title>`.
</ParamField>

<ParamField path="metaDescription" type="string">
  SEO meta description.
</ParamField>

**Returns:** The created draft blog post object.

***

### update\_blog\_post\_draft

Edit a blog post's draft buffer (title, slug, body, SEO) without touching the live
post. Publish later with `publish_blog_post`.

**Does not require confirmation** (draft buffer only, live post untouched).

<ParamField path="objectId" type="string" required>
  The blog post id.
</ParamField>

<ParamField path="name" type="string" />

<ParamField path="slug" type="string" />

<ParamField path="postBody" type="string">
  Post body HTML.
</ParamField>

<ParamField path="htmlTitle" type="string" />

<ParamField path="metaDescription" type="string" />

**Returns:** The updated draft blog post object.

***

### publish\_blog\_post

Push a blog post's draft changes live to the public site.

**Requires `confirm: true`.** Without it, returns a WritePreview dry-run.

<ParamField path="objectId" type="string" required>
  The blog post id.
</ParamField>

<ParamField path="confirm" type="boolean">
  Set `true` to apply. Omit or `false` to receive a WritePreview.
</ParamField>

**Returns:** HubSpot's push-live response on success; `WritePreview` if not confirmed.

***

### upsert\_hubdb\_row\_draft

Create a new row (omit `rowId`) or update an existing one (pass `rowId`) in a
HubDB table's draft. Changes are staged and invisible on live pages until
`publish_hubdb_table` is called.

Call `get_hubdb_table` first to inspect column types. Cell values must match each
column's type (for example: `FOREIGN_ID` columns expect arrays, select columns expect
option objects, date columns expect epoch milliseconds).

**Does not require confirmation** (staged in draft, nothing live until publish).

<ParamField path="tableIdOrName" type="string" required />

<ParamField path="rowId" type="string">
  Existing row id to update. Omit to create a new row.
</ParamField>

<ParamField path="values" type="object" required>
  Map of `columnName` to cell value, typed per the column schema.
</ParamField>

**Returns:** The created or updated draft row.

***

### publish\_hubdb\_table

Publish a HubDB table's draft (schema and rows) so the changes appear on live pages.
Any prior HubDB row write is invisible until this is called.

**Requires `confirm: true`.** Without it, returns a WritePreview dry-run.

<ParamField path="tableIdOrName" type="string" required />

<ParamField path="confirm" type="boolean">
  Set `true` to apply. Omit or `false` to receive a WritePreview.
</ParamField>

**Returns:** HubSpot's publish-table response on success; `WritePreview` if not
confirmed.

***

### reset\_hubdb\_draft

Discard all unpublished draft changes on a HubDB table, reverting the draft to the
current live/published version. Destructive: all staged row and schema edits are lost.

**Requires `confirm: true`.** Without it, returns a WritePreview dry-run.

<ParamField path="tableIdOrName" type="string" required />

<ParamField path="confirm" type="boolean">
  Set `true` to apply. Omit or `false` to receive a WritePreview.
</ParamField>

**Returns:** HubSpot's reset-draft response on success; `WritePreview` if not
confirmed.

***

## Code examples

<CodeGroup>
  ```json Request: list_pages theme={null}
  {
    "name": "hubspot__list_pages",
    "arguments": {
      "type": "site",
      "limit": 20
    }
  }
  ```

  ```json Request: get_blog_post (draft) theme={null}
  {
    "name": "hubspot__get_blog_post",
    "arguments": {
      "objectId": "98765432",
      "draft": true
    }
  }
  ```

  ```json Request: list_hubdb_rows theme={null}
  {
    "name": "hubspot__list_hubdb_rows",
    "arguments": {
      "tableIdOrName": "products",
      "limit": 50
    }
  }
  ```

  ```json Request: get_traffic_analytics theme={null}
  {
    "name": "hubspot__get_traffic_analytics",
    "arguments": {
      "breakdown": "sources",
      "timePeriod": "monthly",
      "start": "20260101",
      "end": "20260531"
    }
  }
  ```

  ```json Request: create_blog_post_draft theme={null}
  {
    "name": "hubspot__create_blog_post_draft",
    "arguments": {
      "contentGroupId": "11223344",
      "name": "How to set up your first campaign",
      "slug": "how-to-set-up-first-campaign",
      "postBody": "<p>Getting started is easier than you think...</p>",
      "htmlTitle": "How to set up your first campaign | Acme Blog",
      "metaDescription": "A step-by-step guide to launching your first campaign."
    }
  }
  ```

  ```json Request: publish_blog_post (dry-run preview) theme={null}
  {
    "name": "hubspot__publish_blog_post",
    "arguments": {
      "objectId": "98765432"
    }
  }
  ```

  ```json Request: publish_blog_post (apply with confirm) theme={null}
  {
    "name": "hubspot__publish_blog_post",
    "arguments": {
      "objectId": "98765432",
      "confirm": true
    }
  }
  ```

  ```json Request: upsert_hubdb_row_draft (create) theme={null}
  {
    "name": "hubspot__upsert_hubdb_row_draft",
    "arguments": {
      "tableIdOrName": "products",
      "values": {
        "name": "Starter Kit",
        "price": 299,
        "active": true
      }
    }
  }
  ```

  ```json Request: publish_hubdb_table (requires confirm) theme={null}
  {
    "name": "hubspot__publish_hubdb_table",
    "arguments": {
      "tableIdOrName": "products",
      "confirm": true
    }
  }
  ```
</CodeGroup>

<Note>
  Tool names are namespaced `hubspot__<tool>` in Claude/ChatGPT (the AI invokes
  these automatically). The bare names (e.g. `list_pages`) are used here for
  readability.
</Note>

<Note>
  An expired or revoked access token returns `AccessTokenExpiredError`. Because
  HubSpot uses a `refresh_token` flow, silent renewal normally prevents this. If
  it does surface, reconnect via the portal to issue a fresh token pair. See
  [Errors & limits](/developers/errors-and-limits).
</Note>

<Note>
  HubSpot's public-app rate limit is **110 requests per 10 seconds per connected
  account**. On a `429`, the client retries up to 3 times with backoff (honoring
  `Retry-After` when present), so a chatty session self-heals. Per-request timeout
  is 20s. `get_traffic_analytics` uses HubSpot's legacy v2 analytics API with dates
  in `YYYYMMDD` format. A `403` almost always means the connected portal's plan or
  granted scopes do not cover that surface. See
  [Errors & limits](/developers/errors-and-limits).
</Note>

<Note>
  This connector is **multi-instance**: a tenant can connect several HubSpot portals,
  each as its own connector instance with a separately vaulted `refresh_token`.
</Note>
