# Hubspot Contacts

> Use when the user wants to search, read, create, update, delete, or otherwise manage HubSpot CRM contacts (people records, emails, leads, subscribers) via the `hubspot` command-line tool.

- Skill: `dipankar/hubspot-contacts` (Agent Skill)
- Install (CLI): `npx skillmds@latest add dipankar/hubspot-contacts`
- Raw SKILL.md: https://api.skillmd.com/api/skills/dipankar/hubspot-contacts/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: dipankar (https://skillmd.com/u/dipankar)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/dipankar/hubspot-contacts

---


# HubSpot Contacts (via `hubspot` CLI)

Drive HubSpot contact operations through the `hubspot` CLI. Every command emits a JSON envelope (`{"success": true, "data": ...}`) suitable for parsing.

## Prerequisite: authentication

The CLI reads `HUBSPOT_ACCESS_TOKEN` (a HubSpot private-app token) from the environment. If a command returns exit code 3 with `AUTH_ERROR`, tell the user:

```bash
export HUBSPOT_ACCESS_TOKEN=<private-app-token>
```

Multi-account users can pass `--profile <name>` (reads `[profiles.<name>]` from `~/.config/hubspot-cli/config.toml`).

## Canonical commands

| Intent | Command |
| --- | --- |
| List recent contacts | `hubspot crm contacts list --limit 10` |
| Paginate | `hubspot crm contacts list --limit 50 --after <next_cursor>` |
| Request extra properties | `hubspot crm contacts list --properties phone,company,lifecyclestage` |
| Get by ID | `hubspot crm contacts get 12345` |
| Create from flags | `hubspot crm contacts create --email jane@example.com --firstname Jane --lastname Doe` |
| Create from JSON | `hubspot crm contacts create --properties '{"email":"jane@x.com","lifecyclestage":"lead"}'` |
| Update | `hubspot crm contacts update 12345 --firstname Janet` |
| Delete (archive) | `hubspot crm contacts delete 12345 --yes` |
| Simple search | `hubspot crm contacts search --query "email:*@acme.com" --limit 25` |
| Filtered search | `hubspot crm contacts search --filter-groups '[{"filters":[{"propertyName":"lifecyclestage","operator":"EQ","value":"customer"}]}]'` |
| Batch create | `hubspot crm contacts batch-create --inputs '[{"properties":{"email":"a@x.com"}},{"properties":{"email":"b@x.com"}}]'` |
| Discover properties | `hubspot discover properties contacts` |

## Filter-group operators

Common `operator` values for `--filter-groups`: `EQ`, `NEQ`, `GT`, `GTE`, `LT`, `LTE`, `BETWEEN`, `IN`, `NOT_IN`, `HAS_PROPERTY`, `NOT_HAS_PROPERTY`, `CONTAINS_TOKEN`, `NOT_CONTAINS_TOKEN`. Filter groups are OR'd; filters within a group are AND'd.

## Default vs custom properties

Only `email`, `firstname`, `lastname` come back by default. For anything else (phone, company, `lifecyclestage`, `hs_lead_status`, `hubspot_owner_id`, custom properties) pass `--properties prop1,prop2,...`. Use `hubspot discover properties contacts` to list every valid property name.

## Output envelope

```json
{
  "success": true,
  "data": { "id": "12345", "properties": { "email": "...", "firstname": "..." } },
  "paging": { "next_cursor": "abc", "has_more": true }
}
```

List endpoints put an array in `data`. Pagination: when `paging.has_more` is true, call again with `--after <paging.next_cursor>`.

## Error codes (JSON `error.code`, exit code)

| Code | Exit | Action |
| --- | --- | --- |
| `AUTH_ERROR` | 3 | Tell the user to set `HUBSPOT_ACCESS_TOKEN` |
| `VALIDATION_ERROR` / `DUPLICATE_EMAIL` | 5 | Show `error.message`; fix input |
| `NOT_FOUND` | 6 | Contact ID doesn't exist or was archived |
| `RATE_LIMIT` | 7 | Back off using `error.retry_after_seconds` |
| `SERVER_ERROR` | 8 | Transient; retry once |

## Required scopes

- Read: `crm.objects.contacts.read`
- Write: `crm.objects.contacts.write`
- Search: `crm.objects.contacts.read`

## Tips for the agent

- Prefer `search` over `list` when the user describes a filter ("contacts in lifecycle stage customer"). `list` has no filtering.
- Always pass `--limit` when the user says "a few" / "some" to keep responses small.
- Use `--output json-pretty` only when showing the raw JSON to the user; use the default `json` when piping into `jq` or parsing.
- Never echo the access token back to the user.

