Skip to content

Built for people who want to own their automations. Join the waitlist for an invite.

Package listing

@kody/hubspot

README.md

243 lines · 9.6 KB · Markdown

@kody/hubspot

Intent

Provide reusable, account-agnostic HubSpot CRM helpers so Kody agents can read contacts, companies, deals, tickets, and other CRM objects through a saved hubspot / hubspot-* OAuth integration or a private-app token — without hand-rolling REST. Mutations are previewable with dryRun: true and only run live after confirm: true.

This listing is meant to be forked. After you fork, connect your HubSpot portal. Do not treat the live @kody/hubspot package storage as yours.

When To Use

  • List, get, or search contacts, companies, deals, and tickets
  • Read any other CRM object type through the generic object helpers
  • Preview or apply contact / company / deal / ticket writes after confirmation
  • Call an unwrapped HubSpot REST path through ./request
  • Connect more than one HubSpot portal via integrationName / account

Auth

HubSpot has no built-in Kody OAuth app. Choose one lane:

LaneWhen to useCredential
OAuth (recommended)Multi-account, refresh tokens, shared helpersSaved integration hubspot or hubspot-<purpose>
Private app tokenFastest for a single portalUser secret hubspotPrivateAppToken (or hubspotPrivateAppToken-<purpose>)

Both lanes send Authorization: Bearer …. Required API hosts: api.hubapi.com and api.hubspot.com. Approve them in the account secrets UI.

Lane A — BYO OAuth
  1. Create an OAuth app from https://developers.hubspot.com/docs/apps/developer-platform/build-apps/create-an-app
  2. Set the redirect URI exactly to https://kody.codes/connect/oauth
  3. Enable the required CRM scopes listed below (oauth is required on every HubSpot OAuth app). Add crm.objects.custom.read as an optional scope if you need custom objects on Enterprise portals.
  4. Connect while signed in to Kody:

https://kody.codes/connect/oauth?provider=hubspot&authorizeUrl=https%3A%2F%2Fapp.hubspot.com%2Foauth%2Fauthorize&tokenUrl=https%3A%2F%2Fapi.hubapi.com%2Foauth%2Fv3%2Ftoken&apiBaseUrl=https%3A%2F%2Fapi.hubapi.com&scopes=oauth%20crm.objects.contacts.read%20crm.objects.contacts.write%20crm.objects.companies.read%20crm.objects.companies.write%20crm.objects.deals.read%20crm.objects.deals.write%20tickets&flow=confidential&pkce=false&allowedHosts=api.hubapi.com%2Capi.hubspot.com&dashboardUrl=https%3A%2F%2Fdevelopers.hubspot.com%2Fdocs%2Fapps%2Fdeveloper-platform%2Fbuild-apps%2Fcreate-an-app&extraAuthorizeParams=optional_scope%3Dcrm.objects.custom.read

  1. Paste the HubSpot client id and client secret into the Kody wizard (never into chat). Approve hosts api.hubapi.com and api.hubspot.com.
  2. Reconnect later with https://kody.codes/connect/oauth?provider=hubspot

OAuth authorize URL: https://app.hubspot.com/oauth/authorize. Token URL: https://api.hubapi.com/oauth/v3/token. Flow: confidential (client secret). HubSpot scopes are space-separated.

To connect a second portal, change provider (for example provider=hubspot-work) and pass integrationName: 'hubspot-work' on every call.

Lane B — Private app token
  1. Create a private app from https://developers.hubspot.com/docs/apps/legacy-apps/private-apps/overview (Development → Legacy apps) and grant the same CRM scopes.
  2. Save the access token (do not paste the value in chat):

https://kody.codes/account/secrets/new?name=hubspotPrivateAppToken&description=HubSpot%20private%20app%20access%20token&allowedHosts=api.hubapi.com%2Capi.hubspot.com&scope=user

For a second portal/token, use a distinct secret name such as hubspotPrivateAppToken-work and pass secretName: 'hubspotPrivateAppToken-work' (or account: 'work', which resolves to hubspot-work / hubspotPrivateAppToken-work).

Scopes

ScopeNeeded for
oauthRequired on every HubSpot OAuth app; ./account, ./smoke-test
crm.objects.contacts.read./list-contacts, ./get-contact, ./search-contacts
crm.objects.contacts.write./create-contact, ./update-contact
crm.objects.companies.read./list-companies, ./get-company, ./search-companies
crm.objects.companies.write./create-company, ./update-company
crm.objects.deals.read./list-deals, ./get-deal, ./search-deals
crm.objects.deals.write./create-deal, ./update-deal
ticketsTicket reads and writes
crm.objects.custom.readGeneric custom-object reads (Enterprise, optional)

If HubSpot returns 401/403 or a missing-scope error, helpers throw a message that names the missing scope and the next setup URL (reconnect OAuth with that scope, or save a private-app token).

Multiple accounts

Every export accepts:

  • integrationName / integration — exact saved OAuth name (hubspot-work)
  • accountworkhubspot-work; hubspot-work used as-is; omitted → hubspot
  • secretName — private-app token secret override
  • auth'oauth' or 'privateApp' when both exist

Do not hard-code a personal portal id or alias.

Safety

Mutating helpers require confirm: true. Pass dryRun: true to inspect the REST payload without calling HubSpot. ./request treats GET and CRM search POST as read-only; other POST / PUT / PATCH / DELETE need confirmation.

Do not write live CRM records from a smoke test.

Exports

ExportDescription
.Package overview, connect URLs, export map
./accountsResolve integration/secret names and report what is connected
./smoke-testLocal helper checks plus optional live portal read (no email)
./accountAuthenticated portal id presence / account type / timezone
./objectsGeneric list for any CRM object type
./list-objectsGeneric list (objectType required)
./get-objectGeneric get (objectType + id)
./search-objectsGeneric search
./list-contacts / ./get-contact / ./search-contactsContact reads
./create-contact / ./update-contactContact writes (dryRun / confirm)
./list-companies / ./get-company / ./search-companiesCompany reads
./create-company / ./update-companyCompany writes
./list-deals / ./get-deal / ./search-dealsDeal reads
./create-deal / ./update-dealDeal writes
./list-tickets / ./get-ticket / ./search-ticketsTicket reads
./create-ticket / ./update-ticketTicket writes
./requestGeneric REST escape hatch
./typesShared TypeScript types

Smoke test

import smokeTest from 'kody:@kody/hubspot/smoke-test'

export default async function main() {
	return await smokeTest()
}

Without credentials this returns { ok: true, live: false } plus the connect and private-app URLs. After OAuth or a private-app token is saved it reads /account-info/v3/details and returns { live: true, hasPortalId } without email.

Preview a mutation without credentials:

import createContact from 'kody:@kody/hubspot/create-contact'

export default async function main() {
	return await createContact({
		properties: { email: 'pat@example.com', firstname: 'Pat' },
		dryRun: true,
	})
}

Examples

import listContacts from 'kody:@kody/hubspot/list-contacts'
import listDeals from 'kody:@kody/hubspot/list-deals'

export default async function main() {
	const contacts = await listContacts({ limit: 10 })
	const deals = await listDeals({ limit: 10 })
	return { contactCount: contacts.items.length, dealCount: deals.items.length }
}
import getObject from 'kody:@kody/hubspot/get-object'
import searchObjects from 'kody:@kody/hubspot/search-objects'

export default async function main() {
	const listing = await getObject({ objectType: 'p_listings', id: '123' })
	const matches = await searchObjects({
		objectType: 'contacts',
		query: 'acme',
		limit: 10,
	})
	return { listingId: listing.id, matches: matches.items.length }
}
import createDeal from 'kody:@kody/hubspot/create-deal'

export default async function main() {
	const preview = await createDeal({
		properties: { dealname: 'Acme renewal', amount: '12000' },
		dryRun: true,
	})
	// After the user confirms the exact deal name and amount:
	return await createDeal({
		properties: { dealname: 'Acme renewal', amount: '12000' },
		confirm: true,
	})
}

Unwrapped REST:

import request from 'kody:@kody/hubspot/request'

export default async function main() {
	return await request({
		path: '/crm/v3/objects/contacts',
		query: { limit: 5, properties: 'email,firstname' },
	})
}

Notes

  • REST base: https://api.hubapi.com
  • Helpers project slim CRM records (id, selected properties, timestamps). Use ./request when you need extra fields or associations payloads.
  • Generic writes stay on ./request. Typed create/update helpers cover only contacts, companies, deals, and tickets.
  • Look up a contact by email with idProperty: 'email'.
  • This package is not affiliated with or endorsed by HubSpot, Inc.

Branding

The community icon is HubSpot's official orange sprocket (#FF7A59) from the HubSpot brand mark as published in Simple Icons. Paths are unmodified. HubSpot® is a trademark of HubSpot, Inc.

Share this listing at https://kody.codes/@kody/hubspot

Docs