HubSpot
A HubSpot portal that answers the CRM, marketing, CMS, email, automation and engagements APIs the official Python SDK calls, plus HubSpot's remote MCP paths.
Vendor reference: https://developers.hubspot.com/docs/api/overview ↗. Machine-readable: compat/hubspot.json.
Surfaces
| Kind | Path | Notes |
|---|---|---|
| REST | /crm/v3 | objects, search, batch, properties, pipelines; associations under /crm/v4; schemas under /crm-object-schemas/v3 |
| REST | /marketing/v3 | also /cms/v3, /email/public/v1, /automation/v4, /engagements/v1, /account-info/v3 |
| OAuth | /oauth | private-app tokens or OAuth2 access tokens, and HubSpot's remote-MCP OAuth paths |
| Webhooks | webhook subscriptions | CRM events delivered to your endpoint |
| MCP | /mcp | also /anthropic, /openai and the bare origin, as mcp.hubspot.com serves them |
API versions: CRM v3 and v4, Marketing v3, CMS v3, Automation v4, Engagements v1
Supported
- The official Python SDK (
hubspot-api-client) unmodified, withhostpointed at the replica - CRM search with
filterGroups, batch read, and list and get across object types - Associations with HubSpot's
HUBSPOT_DEFINEDtype ids and portal-definedUSER_DEFINEDlabels X-HubSpot-RateLimit-*headers on every response, refilling per ten-second interval with a daily countdown- HubSpot's error envelope
Not supported
- Numeric record ids for generated records (ids look like
acct-0)
Known differences and test guidance
| Scenario | Difference from HubSpot | In your tests |
|---|---|---|
| Record ids | Generated records use string ids such as acct-0, not numeric ids; created records get numeric ids from 9000001. Engagements also answer to a numeric alias. | Treat ids as opaque strings; do not do arithmetic on them or parse toObjectId as a number. |
| Rate limits | Rate-limit headers are sent but the limit is not enforced by default. | Assert that your client reads the headers; inject throttle to test a 429. |
| Campaign metrics | Metrics follow the campaign's email engagement events; a campaign with no events falls back to a deterministic synthetic curve. | Assert on consistency between events and totals, not on specific values. |
| State between tests | Created records are kept in memory, visible to every read path, and dropped by a reset or state change. | Reset between tests rather than cleaning up by hand. |
Fault injection
| Key | What the client sees |
|---|---|
throttle | 429 RATE_LIMITS with policy TEN_SECONDLY_ROLLING and Retry-After |
fail_next | The next N calls fail as throttle does, then clear |
quota_limit | 429 RATE_LIMITS with policy DAILY ("You have reached your daily limit.") |
error_rate | 500 INTERNAL_ERROR for the given fraction of calls |
latency_ms | Fixed added latency on every data-plane request |
Applies to every system
- The hosted data plane is read-only: a write is refused with
403 writes_disabled. A SOQL, GraphQL or searchPOSTis a read and is answered. - Faults are injected through
POST /_admin/faultson a simulator you run yourself;POST /_admin/faults/resetclears them. - Rate limits do not happen on their own unless a page says so. Use fault injection to exercise a client's backoff.
- Distributions come from aggregated metadata sketches of data Eon backs up; no customer records; all Era data is simulated.