Integrations
HubSpot SDK: get started
The complete HubSpot API through typed application or agent clients
Use an ordinary App, or an Agent when the workload needs agent identity. Alter builds provider requests from a reviewed contract and applies the same connection authorization, policy, approvals, and audit records as raw requests.
Install
Section titled “Install”npm install @alter-ai/sdk @alter-ai/hubspot# Python (installs core automatically)pip install alter-sdk-hubspotThe provider package includes only HubSpot types and response schemas and pins its contract version.
Connect and call
Section titled “Connect and call”Configure HubSpot, request the scopes the application needs, and retain each customer’s grant ID after Connect consent. Alter supplies the credential; it never belongs in operation input.
import { App } from "@alter-ai/sdk";import { HubSpotIntegration } from "@alter-ai/hubspot";const app = new App({ apiKey: process.env.ALTER_API_KEY! });try { const client = new HubSpotIntegration(app, { grantId: process.env.HUBSPOT_GRANT_ID! }); const result = await (await client.contacts.getContacts({ query: { limit: 10 } })).wait(); console.log(result.bodyJson());} finally { await app.close();}from alter_sdk import Appfrom alter_sdk_hubspot import HubSpotIntegration
async def first_call(api_key: str, grant_id: str): async with App(api_key=api_key) as app: client = HubSpotIntegration(app, {"grant_id": grant_id}) call = await client.contacts.get_contacts({"query": {"limit": 10}}) result = await call.wait() return result.body_json()Complete versioned contract
Section titled “Complete versioned contract”HubSpot publishes one OpenAPI document per API in its public spec catalog. Contract 2026-10-28 merges the 2026-09 document of every HubSpot API a connected account’s OAuth token can call: 1,009 operations from HubSpot’s documents across the CRM (contacts, companies, deals, tickets, notes, emails, tasks, calls, meetings and every other object, associations, owners, pipelines, properties and schemas), CMS, marketing, conversations, automation, commerce, files, events and account settings. The API reference links every operation to its complete input fields, response models, TypeScript and Python examples, required scopes and policy identity. Browse by resource under this provider’s API reference in the Integrations sidebar, or download the merged OpenAPI specification. The Developer Portal provider view and alter integrations operations hubspot --version 2026-10-28 also expose the schemas.
Not published, each for a reason recorded in the contract’s provider input:
- HubSpot’s own OAuth token endpoints, and the three APIs HubSpot marks as developer previews in 2026-09 (forecasts, forecast types and lists).
- Operations HubSpot documents only for its developer API key, such as app webhook settings, CRM card and feature-flag definitions, custom workflow action definitions and video-conferencing settings. A connected account’s token cannot call them.
- The webhooks journal, which HubSpot authenticates with the app’s client-credentials token rather than a connected account’s.
- Writes HubSpot does not document for read-only objects (contracts, feedback submissions), and a few routes HubSpot publishes with no reference page and no scope.
Generated calls pin the Alter contract version. HubSpot’s own product, tier, role and subscription requirements still apply. There is no silent version fallback and no automatic write retry or pagination.
alter integrations execute hubspot hubspot.contacts_get_contacts \ --version 2026-10-28 --grant "$HUBSPOT_GRANT_ID" --input '{"query": {"limit": 10}}' --waitArguments use native path, query, headers, and body containers. Array query parameters are sent the way HubSpot’s documents declare them. SDK methods preserve provider field names, decimal lexemes and large integers as strings when necessary. HTTP 204 returns null / None from the typed decoder. A batch HubSpot applied only in part answers HTTP 207 with the failed inputs under errors and their count in numErrors; the call succeeds with that status and body unchanged (some writes happened, so retrying the whole batch would repeat them), and the caller checks errors itself. Binary and text responses use bodyBytes() / body_bytes(); the CLI can save them with --output-file.
Scopes
Section titled “Scopes”Each operation lists the scopes on its HubSpot reference page. HubSpot’s pages say an endpoint “requires one of the following scopes”, so any one listed scope authorizes the call; a route whose object type is a path parameter (/crm/objects/2026-09/{objectType}, associations, properties, pipelines) lists the scopes of every object it can address. A route that accepts more than eight scopes is one of these generic routes, and a grant whose scopes are narrowed below the connection’s cannot call it through Alter: its object type is chosen per call, so a narrowed grant cannot be shown to cover it, and the call is refused rather than allowed on a guess. Call the object-specific operation instead (contacts.get_contacts rather than the generic objects.get_objects), or use a grant with the connection’s full scopes.
With this contract, HubSpot routes the scope model covers are checked against a narrowed grant’s scopes. A grant narrowed below the connection’s scopes that used a path-pinning restriction rule to reach such a route without one of its listed scopes is now refused there with a scope error. To recover, add one of the route’s listed scopes to the grant’s narrowed scopes, or remove the narrowing. The HubSpot connection offers every scope the contract lists. None is selected by default, so the consent screen asks for exactly the scopes the HubSpot app requests. To call an operation, request one of its scopes on the HubSpot connection and in the app’s HubSpot configuration.
Approvals
Section titled “Approvals”A policy rule can require approval for any operation. The call returns a pending approval; wait() resolves once the approver decides, and resume(approvalId) picks a pending approval up after a restart. Approval cards show the operation, its contract version and the request input the sensitivity review marked safe to display.
Uploads and approvals
Section titled “Uploads and approvals”Nine operations upload a file: files.create and files.replace_by_file_id, imports.create_import, hubdb.import_table_draft, data_sources.create_datasource and data_sources.update_datasource, and the three CMS source-code file operations. Every binary input is a JSON object with filename, content_type, and content_base64; Alter sends each body field as a named multipart part, and JSON settings travel as the string fields HubSpot documents (options, importRequest, config, request).
Uploaded file contents and import files are classified sensitive. An upload a policy sends for approval is held with the pending approval, and the approver sees each file masked as its name and size, never its contents. The shared proxy accepts up to 5 MiB of serialized input and encoded upload data, and base64 adds overhead.
Policy and sensitive data
Section titled “Policy and sensitive data”Every write has a reviewed family entry in the provider input; an unreviewed new write fails generation.
payment: commerce payments, invoices, subscriptions, payment links, quotes, line items, discounts, fees, taxes, products, orders, carts, price books, deal splits and currency exchange rates.send: sending a marketing, single-send or transactional email, enrolling a contact in a sequence, sending a conversations or custom-channel message, booking or creating a scheduler meeting (HubSpot sends the invitations), starting a pipeline approval, creating a user (HubSpot sends the invitation) and testing a webhook subscription.admin: CRM schemas, properties, property validations, pipelines and pipeline rules, association labels, object tags, teams, users, currencies, blog settings, event definitions, custom channel accounts, webhook subscriptions, SMTP tokens and uninstalling the app.delete: every removal, including archives, batch archives, GDPR deletes and merges (a merge removes the merged record).- Searches and batch reads are
read. Starting a CRM export or a content audit export isreadandwrite.
Three operations leave the caller holding a HubSpot credential, so Alter refuses them by default on every connection with credential_minting_refused, before any call reaches HubSpot:
| Operation | TypeScript | Python | What the caller would hold |
|---|---|---|---|
hubspot.transactional_email_create_smtp_tokens | transactionalEmail.createSmtpTokens | transactional_email.create_smtp_tokens | a new SMTP API token’s password, which authenticates mail sent through HubSpot |
hubspot.transactional_email_smtp_tokens_password_reset | transactionalEmail.smtpTokensPasswordReset | transactional_email.smtp_tokens_password_reset | the reset SMTP API token’s new password |
hubspot.visitor_identification_create_visitor_identification | visitorIdentification.createVisitorIdentification | visitor_identification.create_visitor_identification | an identification token that signs a chat-widget visitor in as the contact whose email the caller names |
The refusal matches each route under any version segment HubSpot serves, dated or v3, earlier or later than this contract (/marketing/v3/transactional/smtp-tokens, /marketing/transactional/2027-03/smtp-tokens), and the legacy visitor-token route /conversations/v3/visitor-identification/tokens/create, when they are sent as raw calls.
The methods stay in the SDK. An organization admin who needs one allows it with a credential_minting rule naming provider hubspot and the operation, outright or behind an approval; see Allow an operation that hands over a credential.
Payload capture follows the application’s capture setting, on managed and raw proxied calls alike: credentials are removed, every field Alter’s reviewed inventory for the provider marks sensitive is masked in requests and responses, including fields it locates only by operation, and binary bodies, calls that can return a credential and calls carrying raw card data are never stored. Metadata audit is unchanged. A page or blog-post write that sets a password is refused by default with credential_minting_refused until an operator adds a credential_minting allowance naming the operation; the same write without a password runs as usual. Page and blog-post passwords, uploaded file contents and import files are held encrypted with a pending approval and shown masked to the approver. CRM record properties are an open map of each account’s own fields, so a sensitive value in a custom property is not classified; keep approval rules off operations that write such properties, or redact them with a content rule.
The v3 CRM objects routes the earlier HubSpot catalog covered (/crm/v3/objects/contacts, companies and deals) stay cataloged under their existing operation ids and families, so rules written against them keep applying to raw calls. A rule naming one of those ids (or its older path-style form) also governs every 2026-09 route with the same effect on that object, whichever way the object is spelled (contacts or 0-1, deals or 0-3) and including the generic object routes: for archive_contact, that is the single delete, batch archive, GDPR delete and merge. The 2026-09 deletes carry the write family as well as delete; the retained v3 deletes keep delete alone, exactly as before, so an existing rule behaves the same on a v3 call.
The generic object routes (objects.get_objects, objects.delete_object and the others that take an objectType) reach every object, so a rule naming any object’s operation also governs them, and they carry the payment family because they can write commerce objects. A call that spells a known object by name or type id classifies as that object’s own operation.
Maintenance
Section titled “Maintenance”The pinned official catalog and the 105 documents it names, the scopes from HubSpot’s reference pages, their hashes, the generated methods and the policy mapping are checked together. Scheduled checks save upstream candidates and report additions, removals and schema changes; they never replace a published contract automatically. Existing contract versions remain available for installed SDKs and pending approvals.
Resume after a restart
Section titled “Resume after a restart”Persist call.approvalId (TypeScript) or call.approval_id (Python), then construct a fresh provider client and call resume(savedId), followed by wait(). The stored approval selects the operation; this installed package verifies its provider and contract version before attaching the response validator. Mismatches raise ManagedOperationError, never silently decode using another contract. See persist-and-resume examples.
Rules for SDK functions
Section titled “Rules for SDK functions”Policy operation fields also accept the published SDK function IDs listed by alter integrations operations hubspot. Use when.operation for a json_match or conditional require_approval rule, or match.operations for content_match (including redaction and step-up). Conditional approval can also use when.match.operations with parameter conditions. alter policy simulate --operation <function-id> tests that exact function.
Managed calls carry a server-verified function identity. Alter still evaluates policy against the actual method, URL and body after building the request from the authorized connection. Existing endpoint, method, host, catalog-operation and family rules continue to apply unchanged. A raw call can share an endpoint with several SDK functions and matches all of them; a function name never grants permission or bypasses existing restrictions.
For example, this rule requires approval before a marketing email is published or sent:
{ "effect": "require_approval", "when": { "operation": "hubspot.marketing_emails_publish_email" }, "approval": { "approvers": ["reviewer@example.com"] }}Save it with alter policy rules create --app <app-id> --grant <grant-id> --type require_approval --body @rule.json, or select the function in the portal.