# HubSpot

HubSpot is an all-in-one CRM platform that helps businesses manage marketing, sales, customer service, and content management. The Sling HubSpot connector extracts data from the HubSpot REST API, supporting CRM objects (contacts, companies, deals, tickets), engagement data (calls, emails, meetings, notes, tasks), marketing data (forms, emails, lists), and associations.

{% hint style="success" %}
**CLI Pro Required**: APIs require a [CLI Pro token](https://docs.slingdata.io/sling-cli/cli-pro) or [Platform Plan](https://docs.slingdata.io/sling-platform/platform).
{% endhint %}

## Setup

The following credentials and inputs are accepted:

**Secrets:**

* `access_token` **(required)** -> Your HubSpot Legacy App Access Token (starts with `pat-`)

**Inputs:**

* `anchor_date` (optional) -> The starting date for historical data extraction (default: 1 year ago). Format: `YYYY-MM-DD`

### Getting Your Access Token

HubSpot uses **Legacy Apps** (formerly called Private Apps) for API authentication:

1. Log in to your [HubSpot account](https://app.hubspot.com/)
2. Go to **Settings** (gear icon) > **Integrations** > **Private Apps** (now called Legacy Apps)
3. Click **Create a private app** (or **Create a legacy app**)
4. Give your app a name (e.g., "Sling Integration")
5. Go to the **Scopes** tab and add the following scopes based on your needs:

**Required scopes for CRM data:**

* `crm.objects.contacts.read`
* `crm.objects.companies.read`
* `crm.objects.deals.read`
* `crm.objects.owners.read`

**Optional scopes (add based on endpoints you need):**

* `tickets` - For tickets endpoint
* `sales-email-read` - For engagements\_emails
* `crm.objects.marketing_events.read` - For marketing events
* `forms` - For forms and form\_submissions
* `automation` - For workflows

6. Click **Create app**
7. Copy your **Access token** (it starts with `pat-na1-` or similar)

{% hint style="warning" %}
**Important:** Legacy Apps provide a Bearer token for authentication. Do not confuse this with Personal Access Keys (used for the HubSpot CLI), which start with `CiR` and will not work with the REST API.
{% endhint %}

### Using `sling conns`

{% code overflow="wrap" %}

```bash
sling conns set HUBSPOT type=api spec=hubspot-v3 secrets='{ access_token: pat-na1-xxxxxxxxxxxx }'
```

{% endcode %}

### Environment Variable

See [here](https://docs.slingdata.io/sling-cli/environment#dot-env-file-.env.sling) to learn more about the `.env.sling` file.

{% code overflow="wrap" %}

```bash
export HUBSPOT='{ type: api, spec: hubspot-v3, secrets: { access_token: "pat-na1-xxxxxxxxxxxx" } }'
```

{% endcode %}

### Sling Env File YAML

See [here](https://docs.slingdata.io/sling-cli/environment#sling-env-file-env.yaml) to learn more about the sling `env.yaml` file.

```yaml
connections:
  HUBSPOT:
    type: api
    spec: hubspot-v3
    secrets:
      access_token: "pat-na1-xxxxxxxxxxxx"
```

**With anchor date for historical data:**

```yaml
connections:
  HUBSPOT:
    type: api
    spec: hubspot-v3
    secrets:
      access_token: "pat-na1-xxxxxxxxxxxx"
    inputs:
      anchor_date: "2020-01-01"
```

## Replication

Here's an example replication configuration to sync HubSpot data to a PostgreSQL database:

```yaml
source: HUBSPOT
target: MY_POSTGRES

defaults:
  mode: incremental
  object: hubspot.{stream_name}

streams:
  # Core CRM objects with incremental sync
  contacts:
  companies:
  deals:
  tickets:

  # Reference data (full-refresh only)
  owners:
    mode: full-refresh
  deal_pipelines:
    mode: full-refresh
```

**Full refresh for reference data:**

```yaml
source: HUBSPOT
target: MY_POSTGRES

defaults:
  mode: full-refresh
  object: hubspot.{stream_name}

streams:
  # Pipeline and configuration data
  deal_pipelines:
  ticket_pipelines:
  owners:

  # Property definitions
  properties_contacts:
  properties_companies:
  properties_deals:
```

**Sync all endpoints:**

```yaml
source: HUBSPOT
target: MY_POSTGRES

defaults:
  mode: incremental
  object: hubspot.{stream_name}

streams:
  # Sync all available endpoints
  '*':

  # Disable child endpoints if you don't need associations
  contact_to_company:
    disabled: true
  deal_to_contact:
    disabled: true
  deal_to_company:
    disabled: true
  form_submissions:
    disabled: true
```

## Endpoints

### Core CRM Objects

| Endpoint     | Description      | Incremental |
| ------------ | ---------------- | ----------- |
| `contacts`   | Contact records  | Yes         |
| `companies`  | Company records  | Yes         |
| `deals`      | Deal records     | Yes         |
| `tickets`    | Ticket records   | Yes         |
| `products`   | Product catalog  | No          |
| `line_items` | Deal line items  | No          |
| `quotes`     | Quotes/proposals | No          |

### Configuration & Metadata

| Endpoint               | Description                  | Incremental |
| ---------------------- | ---------------------------- | ----------- |
| `owners`               | HubSpot users/owners         | No          |
| `deal_pipelines`       | Deal pipeline stages         | No          |
| `ticket_pipelines`     | Ticket pipeline stages       | No          |
| `properties_contacts`  | Contact property definitions | No          |
| `properties_companies` | Company property definitions | No          |
| `properties_deals`     | Deal property definitions    | No          |

### Engagement Objects

| Endpoint               | Description              | Incremental |
| ---------------------- | ------------------------ | ----------- |
| `engagements_calls`    | Call activity records    | Yes         |
| `engagements_emails`   | Email activity records   | Yes         |
| `engagements_meetings` | Meeting activity records | Yes         |
| `engagements_notes`    | Note activity records    | Yes         |
| `engagements_tasks`    | Task activity records    | Yes         |

### Marketing

| Endpoint           | Description                           | Incremental |
| ------------------ | ------------------------------------- | ----------- |
| `forms`            | HubSpot forms                         | No          |
| `form_submissions` | Form submission data (child of forms) | No          |
| `marketing_emails` | Marketing email templates             | No          |
| `contact_lists`    | Contact lists (legacy API)            | No          |

### Associations (Parent-Child)

| Endpoint             | Description                   | Notes                                     |
| -------------------- | ----------------------------- | ----------------------------------------- |
| `contact_to_company` | Contact-company relationships | Requires `contacts` endpoint to run first |
| `deal_to_contact`    | Deal-contact relationships    | Requires `deals` endpoint to run first    |
| `deal_to_company`    | Deal-company relationships    | Requires `deals` endpoint to run first    |

### Advanced

| Endpoint       | Description               | Incremental |
| -------------- | ------------------------- | ----------- |
| `workflows`    | Automation workflows      | No          |
| `email_events` | Email opens, clicks, etc. | Yes         |

To discover available endpoints:

```bash
sling conns discover HUBSPOT
```

## Incremental Sync

The HubSpot connector uses time-based incremental sync for supported endpoints:

* **First run:** Fetches records modified from `anchor_date` (default: 1 year ago) to present
* **Subsequent runs:** Only fetches records modified after the last sync

Incremental sync is supported for:

* Core CRM objects (contacts, companies, deals, tickets)
* Engagement objects (calls, emails, meetings, notes, tasks)
* Email events

## Associations

Association endpoints (`contact_to_company`, `deal_to_contact`, `deal_to_company`) use a queue-based pattern:

1. First, the parent endpoint runs (e.g., `contacts`)
2. Record IDs are collected into a queue
3. The association endpoint iterates through the queue to fetch relationships

This means you should run the parent endpoint before the association endpoint in your replication.

## HubSpot Scopes

Different endpoints require different HubSpot scopes. If you encounter a `403 MISSING_SCOPES` error, add the required scope to your Legacy App:

| Endpoint Category | Required Scope               |
| ----------------- | ---------------------------- |
| Contacts          | `crm.objects.contacts.read`  |
| Companies         | `crm.objects.companies.read` |
| Deals             | `crm.objects.deals.read`     |
| Tickets           | `tickets`                    |
| Owners            | `crm.objects.owners.read`    |
| Forms             | `forms`                      |
| Workflows         | `automation`                 |
| Marketing Emails  | `content`                    |

## Rate Limiting

The HubSpot API has rate limits that vary by subscription:

* **Free/Starter:** 100 requests/10 seconds, 250K daily
* **Professional:** 190 requests/10 seconds, 625K daily
* **Enterprise:** 190 requests/10 seconds, 1M daily
* **Search API:** 5 requests/second (more restrictive)

The connector automatically:

* Uses conservative rate limiting (5 requests/second)
* Retries with exponential backoff on 429 (rate limit) responses

## Common Use Cases

### Sync Core CRM Data

```yaml
source: HUBSPOT
target: MY_POSTGRES

defaults:
  mode: incremental
  object: crm.{stream_name}

streams:
  contacts:
  companies:
  deals:
  tickets:
  owners:
    mode: full-refresh
```

### Sync Engagement Data

```yaml
source: HUBSPOT
target: MY_POSTGRES

defaults:
  mode: incremental
  object: engagements.{stream_name}

streams:
  engagements_calls:
  engagements_emails:
  engagements_meetings:
  engagements_notes:
  engagements_tasks:
```

### Sync Forms and Submissions

```yaml
source: HUBSPOT
target: MY_POSTGRES

defaults:
  mode: full-refresh
  object: marketing.{stream_name}

streams:
  forms:
  form_submissions:
```

If you are facing issues connecting, please reach out to us at <support@slingdata.io>, on [discord](https://discord.gg/q5xtaSNDvp) or open a Github Issue [here](https://github.com/slingdata-io/sling-cli/issues).
