# @kody/hubspot — agent notes Human setup and intent live in [`README.md`](./README.md). This file is for agents: imports, smoke/dryRun execute snippets, and edge cases. Secrets and OAuth by **name** only — never paste tokens. Do not disable live webhooks or jobs. ## Auth | Call | Resolves to | | --- | --- | | omit | `hubspot` / `hubspotPrivateAppToken` | | `account: 'work'` | `hubspot-work` / `hubspotPrivateAppToken-work` | | `integrationName: 'hubspot-work'` | that exact OAuth name | | `secretName: 'hubspotPrivateAppToken-work'` | that exact private-app secret | | `auth: 'oauth' \| 'privateApp'` | force lane when both exist | Hosts: `api.hubapi.com`, `api.hubspot.com`. REST base: `https://api.hubapi.com`. Do not hard-code a personal portal id or alias. ## Import paths | Export | Import | | --- | --- | | overview / connect URLs / export map | `kody:@kody/hubspot` | | accounts | `kody:@kody/hubspot/accounts` | | account | `kody:@kody/hubspot/account` | | smoke-test | `kody:@kody/hubspot/smoke-test` | | objects | `kody:@kody/hubspot/objects` | | list-objects | `kody:@kody/hubspot/list-objects` | | get-object | `kody:@kody/hubspot/get-object` | | search-objects | `kody:@kody/hubspot/search-objects` | | list-contacts | `kody:@kody/hubspot/list-contacts` | | get-contact | `kody:@kody/hubspot/get-contact` | | search-contacts | `kody:@kody/hubspot/search-contacts` | | create-contact | `kody:@kody/hubspot/create-contact` | | update-contact | `kody:@kody/hubspot/update-contact` | | list-companies | `kody:@kody/hubspot/list-companies` | | get-company | `kody:@kody/hubspot/get-company` | | search-companies | `kody:@kody/hubspot/search-companies` | | create-company | `kody:@kody/hubspot/create-company` | | update-company | `kody:@kody/hubspot/update-company` | | list-deals | `kody:@kody/hubspot/list-deals` | | get-deal | `kody:@kody/hubspot/get-deal` | | search-deals | `kody:@kody/hubspot/search-deals` | | create-deal | `kody:@kody/hubspot/create-deal` | | update-deal | `kody:@kody/hubspot/update-deal` | | list-tickets | `kody:@kody/hubspot/list-tickets` | | get-ticket | `kody:@kody/hubspot/get-ticket` | | search-tickets | `kody:@kody/hubspot/search-tickets` | | create-ticket | `kody:@kody/hubspot/create-ticket` | | update-ticket | `kody:@kody/hubspot/update-ticket` | | request | `kody:@kody/hubspot/request` | | types | `kody:@kody/hubspot/types` | Prefer static `kody:@kody/hubspot/...` imports from `execute`. Do not lead with `packages.invoke`. ## Smoke test (read-only) ```js import smokeTest from 'kody:@kody/hubspot/smoke-test' export default async function main() { return await smokeTest() // Without credentials: { ok: true, live: false } + connect / private-app URLs // With credentials: { live: true, hasPortalId } (no email) } ``` Do not write live CRM records from a smoke test. ## dryRun / confirm 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. ```js import createContact from 'kody:@kody/hubspot/create-contact' export default async function main() { const preview = await createContact({ properties: { email: 'pat@example.com', firstname: 'Pat' }, dryRun: true, }) // After the user confirms the exact properties: return await createContact({ properties: { email: 'pat@example.com', firstname: 'Pat' }, confirm: true, }) } ``` Read helpers: ```js 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 } } ``` Generic objects / unwrapped REST: ```js import getObject from 'kody:@kody/hubspot/get-object' import searchObjects from 'kody:@kody/hubspot/search-objects' import request from 'kody:@kody/hubspot/request' export default async function main() { const listing = await getObject({ objectType: 'p_listings', id: '123' }) const matches = await searchObjects({ objectType: 'contacts', query: 'acme', limit: 10, }) const raw = await request({ path: '/crm/v3/objects/contacts', query: { limit: 5, properties: 'email,firstname' }, }) return { listingId: listing.id, matches: matches.items.length, raw } } ``` ## Edge cases - Look up a contact by email with `idProperty: 'email'`. - 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. - Prefer dedicated helpers over assembling `./request` when one exists.