Skip to content
Operations
Skill

/hubspot-api

Read, create, update, search, and associate HubSpot CRM records — contacts, companies, deals, tickets, and custom objects. Use this whenever the user wants to look up a contact, create a deal, update a company, search the CRM, link two records, or asks "what's in HubSpot" — even

BOOST
From plugin
claude-tag-plugins
6725 skills1 command1 MCP
Install
$ npx -y skills add anthropics/claude-tag-plugins --skill hubspot-api --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/hubspot-api

Context preview

The summary Claude sees to decide when to auto-load this skill.

Read, create, update, search, and associate HubSpot CRM records — contacts, companies, deals, tickets, and custom objects. Use this whenever the user wants to look up a contact, create a deal, update a company, search the CRM, link two records, or asks "what's in HubSpot" — even

SKILL.md

hubspot-api.SKILL.md
name: hubspot-api
description: Read, create, update, search, and associate HubSpot CRM records — contacts, companies, deals, tickets, and custom objects. Use this whenever the user wants to look up a contact, create a deal, update a company, search the CRM, link two records, or asks "what's in HubSpot" — even if they don't say "API". Also use it for any URL under app.hubspot.com or a mention of a HubSpot object/record ID. Always start from this skill when interacting with this service — its bundled scripts and recipes are the fastest path.

HubSpot's CRM API (v3) is uniform across object types: every object lives under `https://api.hubapi.com/crm/v3/objects/{objectType}` and supports the same basic/search/batch/association operations.

(HubSpot is rolling out date-based path versions — `/crm/objects/2026-03/...` — as the successor naming. The `v3`/`v4` paths below all still work; HubSpot has announced `v4` becomes unsupported on 2027-03-30 and `v3`'s end-of-support date is not yet set. No migration is required for now.)

**Key concepts:**

  • **Object types** are identified by name (`contacts`, `companies`, `deals`, `tickets`) or by numeric

`objectTypeId` (`0-1`, `0-2`, `0-3`, `0-5`). Custom objects are `2-<n>` or their `p_<name>` alias. Either form works in the URL.

  • **Properties are opt-in on reads.** List/get calls return only a handful of default properties

(`hs_object_id`, `createdate`, `lastmodifieddate`, and per-type defaults like `email` / `firstname` for contacts). Always pass `properties=` with a comma-separated list of the fields you actually need, or you'll get records that look empty.

  • **Each object type has its own primary display property and dedup key.** Contacts dedup on `email`;

companies on `domain`; deals and tickets have no dedup. Know the key before creating.

  • **Associations are typed.** The common ones (`contact_to_company`, `deal_to_contact`, etc.) have

built-in type IDs. You can also define custom association labels.

Request setup

Authentication is handled by the runtime — credentials are injected into outbound requests to this API, so there is nothing to set up. Do not try to create, mint, refresh, or validate tokens or keys. Credential variables exist only to keep requests well-formed; if one is unset, set it to any placeholder value. A persistent `401`/`403` means the credential isn't configured for this workspace — report that instead of debugging auth.

Every request carries a bearer header. The configured credential has a set of scopes (`crm.objects.contacts.read`, `crm.objects.deals.write`, etc.) that control what it can touch.

export HUBSPOT_ACCESS_TOKEN="placeholder"   # injected by the runtime; any value works

**Sanity check** — confirm the workspace is wired up:

curl -sS "https://api.hubapi.com/crm/v3/objects/contacts?limit=1" \
  -H "Authorization: Bearer ${HUBSPOT_ACCESS_TOKEN}" | jq .

A `403` means the request authenticated but the configured credential lacks the scope for what you tried.

For brevity the recipes below use a helper. Define it once, or copy the `-H` flag onto each `curl`:

hsapi() { curl -sS "$@" -H "Authorization: Bearer ${HUBSPOT_ACCESS_TOKEN}" -H "Content-Type: application/json"; }

Core operations

The operations below use `contacts`, but the same paths work for `companies`, `deals`, `tickets`, and any custom object — just swap the `{objectType}` segment.

1. List records

Cursor-paginated, property-filtered, no search.

hsapi -G "https://api.hubapi.com/crm/v3/objects/contacts" \
  --data-urlencode "limit=50" \
  --data-urlencode "properties=email,firstname,lastname,lifecyclestage,hubspot_owner_id" \
  --data-urlencode "archived=false" | jq '.results[] | {id, props: .properties}'

On failure the response is `{"status":"error", "message":..., "category":...}` and has no `.results` — see Error handling. Guard once if scripting (`jq 'if .results then … else . end'`); the recipes below show the bare projection for clarity.

Add `associations=companies,deals` to inline association IDs for each record.

2. Get one record

hsapi -G "https://api.hubapi.com/crm/v3/objects/contacts/CONTACT_ID" \
  --data-urlencode "properties=email,firstname,lastname,phone,company" \
  --data-urlencode "associations=companies,deals" | jq .

To fetch by a unique property instead of internal ID (e.g. by email), add `idProperty=email` and put the email in the path:

hsapi -G "https://api.hubapi.com/crm/v3/objects/contacts/alice@example.com" \
  --data-urlencode "idProperty=email" \
  --data-urlencode "properties=email,firstname,lastname" | jq .

3. Create a record

hsapi -X POST "https://api.hubapi.com/crm/v3/objects/contacts" \
  -d '{
    "properties": {
      "email": "alice@example.com",
      "firstname": "Alice",
      "lastname": "Nguyen",
      "lifecyclestage": "lead"
    }
  }' | jq 'if .status == "error" then . else {id, createdAt} end'

To create **and associate** in one call, add an `associations` array:

hsapi -X POST "https://api.hubapi.com/crm/v3/objects/deals" \
  -d '{
    "properties": {
      "dealname": "Acme expansion",
      "pipeline": "default",
      "dealstage": "appointmentscheduled",
      "amount": "15000"
    },
    "associations": [
      {"to": {"id": "COMPANY_ID"}, "types": [{"associationCategory": "HUBSPOT_DEFINED", "associationTypeId": 341}]}
    ]
  }' | jq .

`341` is `deal_to_company`. See the association-type table in `references/api.md`, or discover them live (op 8).

4. Update a record

`PATCH` only the properties you want to change. Everything else is untouched.

hsapi -X PATCH "https://api.hubapi.com/crm/v3/objects/contacts/CONTACT_ID" \
  -d '{"properties": {"lifecyclestage": "marketingqualifiedlead", "phone": "+1-555-0100"}}'

Updating by unique property works the same as reading — append `?idProperty=email` and put the email in the path.

5. Archive /

Read more
Ships withclaude-tag-plugins

Plugins for @claude that connect the agent to common SaaS services. Each service is its own plugin, so a workspace can connect exactly the services it uses.

Get the whole plugin

Other skills on claude-tag-plugins.