hubspot-api-python

Official Python client for HubSpot's CRM, Marketing, CMS, and Automation APIs, with lazy per-product discovery and built-in pagination helpers.

SDK
PyPI
v12.0.0
433stars
Apache License 2.0

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
53/100Fair
Development Activity4
Maintenance44
Community76
Maturity60
Momentum28

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
52/100Fair
Architecture68
Code Quality50
Innovation55
Learning Curve35

hubspot-api-client is HubSpot’s official Python SDK for the v3 HubSpot API, generated from HubSpot’s OpenAPI specifications and published as the hubspot-api-client package (imported as hubspot). It exposes every major HubSpot product surface — CRM objects (contacts, companies, deals, tickets, custom objects), CMS, Marketing, Automation, Conversations, Events, Files, Settings, Webhooks, and OAuth — behind a single HubSpot client instance, so consumers authenticate once with an access token and then reach any domain through lazily-instantiated, per-domain API clients.

Beyond the generated request/response plumbing, the SDK layers a few hand-written conveniences on top: a get_all() helper that transparently paginates list endpoints, a do_search() helper for HubSpot’s CRM search API, a request-signature validator for verifying inbound webhook payloads, and an api_request() escape hatch for calling endpoints the generated client doesn’t wrap yet.

What You Get

  • A single HubSpot(access_token=...) client that lazily exposes every HubSpot product API (crm, cms, marketing, automation, conversations, events, files, settings, webhooks, oauth) as properties.
  • Full generated CRUD, batch, search, and association clients for CRM objects (contacts, companies, deals, tickets, quotes, custom objects) and CMS/marketing resources.
  • A built-in get_all() pagination helper and do_search() convenience method so you don’t hand-roll HubSpot’s cursor-based paging.
  • Webhook signature validation (hubspot.utils.signature.Signature.is_valid) for verifying inbound HubSpot webhook requests.
  • An api_request() escape hatch for calling any HubSpot endpoint the generated client hasn’t wrapped yet, using the same authenticated client config.

Common Use Cases

  • Syncing CRM records from an internal app - a backend service creates/updates HubSpot contacts, companies, and deals via crm.contacts.basic_api so sales reps see the same data without manual entry.
  • Building a HubSpot marketplace app - an app developer implements OAuth token exchange with hubspot.oauth and verifies incoming webhook payloads with Signature.is_valid before processing lifecycle events.
  • Bulk data migration into HubSpot - a migration script uses batch APIs (batch_api.create/update) and get_all() pagination to import or reconcile thousands of contacts/companies from a legacy CRM.
  • Custom reporting on HubSpot data - an analytics job uses do_search() with filter groups (e.g. by lastmodifieddate) to pull recently changed CRM objects into a warehouse.

Under The Hood

Architecture The package is generated via OpenAPI Generator and organized as a two-tier design: a small hand-written facade (hubspot/client.py’s Client, subclassed by hubspot/hubspot.py’s HubSpot) holds a config dict (access_token, api_key, retry, etc.), while each top-level HubSpot product domain — crm, cms, marketing, automation, conversations, events, files, settings, webhooks, oauth, communication_preferences — is exposed as a lazy property that imports and constructs a per-domain Discovery class (e.g. hubspot/discovery/crm/discovery.py) inheriting from DiscoveryBase (hubspot/discovery/discovery_base.py). DiscoveryBase._default_api_factory is the single chokepoint that turns the shared config into a configured, generated ApiClient for whichever domain was accessed, so every lazily-constructed API client depends on that one factory for translating api_key/access_token/retry into the generated Configuration object — changing it would ripple through every domain. Below discovery, each object namespace (contacts, companies, deals, etc.) is fully machine-generated OpenAPI client code (configuration.py, api_client.py, rest.py, exceptions.py, plus one file per operation group like basic_api.py, batch_api.py, search_api.py).

Tech Stack Pure Python 3.7+ with no web framework — it’s an API client only. Runtime dependencies (from setup.py) are requests>=2.31, urllib3>=1.15,<3.0, six, certifi, python-dateutil, and an importlib-metadata backport for Python <3.8. Packaging uses plain setuptools (setup.py plus a VERSION file read at build time) rather than pyproject.toml/poetry. Dev dependencies are pytest and black. Releases are published to PyPI automatically via .github/workflows/pythonpublish.yml (twine) when a GitHub Release is created.

Code Quality Tests live in tests/spec (one file per API domain, mostly asserting that lazily-discovered properties resolve to the right generated API classes, e.g. tests/spec/crm/test_contacts.py) and tests/integration (real network calls against a live HubSpot sandbox using HUBSPOT_ACCESS_TOKEN/HUBSPOT_DEVELOPER_API_KEY/HUBSPOT_APP_ID secrets, run via make integration_test in a separate CI workflow). Error handling is per-domain generated ApiException classes rather than one shared exception type, consistent with OpenAPI Generator output but meaning callers must import the right exception per module. There’s no repo-wide type checking (no mypy config) and no lint step in CI beyond Black formatting (make fmt); .github/workflows/pythonapp.yml runs make test on push/PR to master.

API Design The library’s main ergonomic idea is the lazy discovery-property pattern — HubSpot().crm.contacts.basic_api.create(...) — so importing HubSpot gives access to the entire generated API surface without eagerly importing dozens of submodules. A hand-written convenience layer sits on top of the generated CRUD/search clients: get_all() for transparent pagination, do_search() for HubSpot’s CRM search API, and api_request() as a passthrough for endpoints the generator hasn’t wrapped yet. Outside of that convenience layer, the request/response surface follows standard OpenAPI-generator Python SDK conventions rather than anything novel to this project.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search