{
  "connector": "hubspot",
  "label": "HubSpot",
  "vendor_docs": "https://developers.hubspot.com/docs/api/overview",
  "summary": "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.",
  "surfaces": [
    {
      "kind": "REST",
      "path": "/crm/v3",
      "notes": "objects, search, batch, properties, pipelines; associations under `/crm/v4`; schemas under `/crm-object-schemas/v3`"
    },
    {
      "kind": "REST",
      "path": "/marketing/v3",
      "notes": "also `/cms/v3`, `/email/public/v1`, `/automation/v4`, `/engagements/v1`, `/account-info/v3`"
    },
    {
      "kind": "OAuth",
      "path": "/oauth",
      "notes": "private-app tokens or OAuth2 access tokens, and HubSpot's remote-MCP OAuth paths"
    },
    {
      "kind": "Webhooks",
      "path": "webhook subscriptions",
      "notes": "CRM events delivered to your endpoint"
    },
    {
      "kind": "MCP",
      "path": "/mcp",
      "notes": "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": [
    {
      "item": "The official Python SDK (`hubspot-api-client`) unmodified, with `host` pointed at the replica"
    },
    {
      "item": "CRM search with `filterGroups`, batch read, and list and get across object types"
    },
    {
      "item": "Associations with HubSpot's `HUBSPOT_DEFINED` type ids and portal-defined `USER_DEFINED` labels"
    },
    {
      "item": "`X-HubSpot-RateLimit-*` headers on every response, refilling per ten-second interval with a daily countdown"
    },
    {
      "item": "HubSpot's error envelope"
    }
  ],
  "unsupported": [
    {
      "item": "Numeric record ids for generated records (ids look like `acct-0`)"
    }
  ],
  "differences": [
    {
      "scenario": "Record ids",
      "difference": "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.",
      "guidance": "Treat ids as opaque strings; do not do arithmetic on them or parse `toObjectId` as a number."
    },
    {
      "scenario": "Rate limits",
      "difference": "Rate-limit headers are sent but the limit is not enforced by default.",
      "guidance": "Assert that your client reads the headers; inject `throttle` to test a 429."
    },
    {
      "scenario": "Campaign metrics",
      "difference": "Metrics follow the campaign's email engagement events; a campaign with no events falls back to a deterministic synthetic curve.",
      "guidance": "Assert on consistency between events and totals, not on specific values."
    },
    {
      "scenario": "State between tests",
      "difference": "Created records are kept in memory, visible to every read path, and dropped by a reset or state change.",
      "guidance": "Reset between tests rather than cleaning up by hand."
    }
  ],
  "faults_supported": [
    {
      "fault": "throttle",
      "behaviour": "429 `RATE_LIMITS` with policy `TEN_SECONDLY_ROLLING` and `Retry-After`"
    },
    {
      "fault": "fail_next",
      "behaviour": "The next N calls fail as `throttle` does, then clear"
    },
    {
      "fault": "quota_limit",
      "behaviour": "429 `RATE_LIMITS` with policy `DAILY` (\"You have reached your daily limit.\")"
    },
    {
      "fault": "error_rate",
      "behaviour": "500 `INTERNAL_ERROR` for the given fraction of calls"
    },
    {
      "fault": "latency_ms",
      "behaviour": "Fixed added latency on every data-plane request"
    }
  ]
}
