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.
Repository Health
Technical Analysis
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 anddo_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_apiso sales reps see the same data without manual entry. - Building a HubSpot marketplace app - an app developer implements OAuth token exchange with
hubspot.oauthand verifies incoming webhook payloads withSignature.is_validbefore processing lifecycle events. - Bulk data migration into HubSpot - a migration script uses batch APIs (
batch_api.create/update) andget_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. bylastmodifieddate) 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.