# HubSpot — CRM API: gotchas

> Canonical URL: https://developers.apideck.com/apis/crm/hubspot/gotchas

## GET /crm/companies

When using a filter or sorting for companies, Hubspot will only return a maximum of 10.000 companies.
## POST /crm/companies

`parent_id` sets the company's parent at creation. In HubSpot the parent is a
relationship between two companies rather than a stored field, so it is set by
supplying the parent company's id here.

If `parent_id` names a company that does not exist, the entire create is rejected and
no company is created — you will not get a company back with the parent silently
dropped.

A company cannot be made its own ancestor. HubSpot rejects a `parent_id` that would
close a loop, at any depth, not just a direct self-reference.

After a successful create, reading the company straight back may briefly return
`parent_id: null`. HubSpot derives the field from the underlying relationship and
updates it a few seconds later; re-read the company if you need to confirm the parent.
Be careful not to feed that early read into a follow-up update — on the update endpoint
an explicit `parent_id` is an instruction, so echoing a stale value back will apply it.

## PATCH /crm/companies/{id}

`parent_id` sets, changes or removes the company's parent. In HubSpot the parent is a
relationship between two companies rather than a stored field.

Send a company id to set or change the parent, `null` to remove it, and omit the field
to leave the current parent untouched. An empty string is treated as omitted, not as a
request to remove the parent.

Changing a parent replaces the hierarchy relationship, but the two companies stay
associated in HubSpot under a plain, unlabelled relationship. The previous parent will
therefore still appear as a related company in the HubSpot UI, without being the parent.
Removing a parent likewise leaves any other relationship between the same two companies
intact.

A company cannot be made its own ancestor. HubSpot rejects a `parent_id` that would
close a loop, at any depth, not just a direct self-reference.

If the parent itself cannot be applied — for example `parent_id` names a company that
does not exist — the whole update is rejected and none of the other fields in the
request are written.

**The reverse is not true, and matters.** The parent is applied before the rest of the
update. If the request then fails for an unrelated reason — most commonly an unknown or
mistyped `custom_fields` key — the parent change has already taken effect and is kept,
even though you receive an error. Do not treat a failed company update as "nothing
happened" when the request also carried `parent_id`: re-read the company to see the
current parent. Sending `parent_id` on its own, in a request that changes nothing else,
avoids this entirely.

**Reading a company back immediately after any parent change returns the previous
`parent_id`, not the new one.** HubSpot derives the field from the underlying
relationship and updates it a few seconds later. This is a trap for read-modify-write
code: if you read a company during that window and send the record back, you will be
sending the *old* parent as an explicit instruction, and it will be applied — silently
undoing the change you just made. Wait for the value to settle before echoing a company
back, or omit `parent_id` from updates that are not meant to change the parent.

An update that changes only `parent_id` still moves the company's `updated_at`, and so
does clearing a parent on a company that has none — even though no field changed. Two
consequences worth planning for: such a company will be returned again by a later
`filter[updated_since]` poll, with nothing visibly different from your previous copy;
and because this registers as a modification on HubSpot's side, it can trigger any of
your own HubSpot workflows that key on company property changes or last-modified date.

## GET /crm/contacts

When using a filter or sorting for contacts, Hubspot will only return a maximum of 10.000 contacts.
## POST /crm/contacts

When creating a contact, it is advised to always include an unique email address since Hubspot uses it to prevent duplicate contacts in HubSpot. More info on the [Hubspot developer docs](https://developers.hubspot.com/docs/api/crm/contacts).
## GET /crm/opportunities

When using a filter or sorting for opportunities, Hubspot will only return a maximum of 10.000 opportunities and omit the company_id, primary_contact_id, company_id, contact_ids, lead_id and contact_id.

HubSpot allows a deal to have no name, and its Search API omits blank properties from results. `title` is always returned, but it will be an **empty string** for such deals rather than a substituted placeholder — an empty `title` means the deal genuinely has no name in HubSpot, not that the value was unavailable. Treat `title` as possibly empty when using it as a display label.

## GET /crm/leads

When using a filter or sorting for leads, Hubspot will only return a maximum of 10.000 leads.
## POST /crm/leads

When creating a lead, it is advised to always include an unique email address since Hubspot uses it to prevent duplicate contacts in HubSpot. More info on the [Hubspot developer docs](https://developers.hubspot.com/docs/api/crm/contacts).
## GET /crm/pipelines

HubSpot does not expose a cross-page total record count on this endpoint. `meta.total_count` will not appear.

## GET /crm/lists

HubSpot's Lists v3 API does not expose `description`, `visibility`, `folder_id`, `owner_id`, `is_favorite`, `is_default`, or `is_system`, and `custom_fields` and `tags` are not mapped, so these fields are not returned for HubSpot. `filter_criteria` is not mapped in v1 (HubSpot's `filterBranch` is available downstream via `includeFilters=true` but is not requested). HubSpot's three list processing types map onto `type` as follows: DYNAMIC lists are returned as `dynamic`; MANUAL and SNAPSHOT lists are both returned as `static`.

## GET /crm/lists/{id}

HubSpot's Lists v3 API does not expose `description`, `visibility`, `folder_id`, `owner_id`, `is_favorite`, `is_default`, or `is_system`, and `custom_fields` and `tags` are not mapped, so these fields are not returned for HubSpot. `filter_criteria` is not mapped in v1 (HubSpot's `filterBranch` is available downstream via `includeFilters=true` but is not requested). HubSpot's three list processing types map onto `type` as follows: DYNAMIC lists are returned as `dynamic`; MANUAL and SNAPSHOT lists are both returned as `static`. A list id that does not exist in HubSpot returns a 404 error from the downstream API.

## GET /crm/lists/{id}/members

HubSpot's list-memberships endpoint returns only the record id and the timestamp at which
the record joined the list, so `object_type` is always `null` for HubSpot members. The
record type is a property of the List itself, not of each member — read `object_type` from
`GET /crm/lists/{id}` and apply it to every member of that list.

Members are returned sorted by record id ascending, not by the order records were added to
the list. For dynamic lists the membership is a saved segment evaluated by HubSpot, so the
result is a point-in-time snapshot rather than a stored set.

## GET /crm/notes

Hubspot will only return activities from the last 30 days when using the `filter[updated_since]` parameter, with a maximum of 10.000 records. More info [here](https://legacydocs.hubspot.com/docs/methods/engagements/get-recent-engagements). Use `filter[type]=call` to get custom fields for call activities. When using both `filter[type]=call` and `filter[updated_since]` together, you will get partial response data as the calls search endpoint does not support many data fields. Use only one filter to get better data fields.
## GET /crm/users

HubSpot owners are mapped to CRM users in Apideck. HubSpot owners and HubSpot users (people who can log into HubSpot) aren't necessarily the same, as you may have owners created through an integration.
## GET /crm/activities

Hubspot will only return activities from the last 30 days when using the `filter[updated_since]` parameter, with a maximum of 10.000 records. More info [here](https://legacydocs.hubspot.com/docs/methods/engagements/get-recent-engagements). Use `filter[type]=call` to get custom fields for call activities. When using both `filter[type]=call` and `filter[updated_since]` together, you will get partial response data as the calls search endpoint does not support many data fields. Use only one filter to get better data fields. `filter[contact_id]` returns **all** activity types (calls, emails, meetings, notes, and tasks) associated with the contact, served by HubSpot's legacy associated-engagements API. Two interactions to note: combining `filter[contact_id]` with `filter[updated_since]` is not supported — the contact filter takes precedence and the date filter is silently ignored (the associated-engagements endpoint has no date filter). And `filter[type]` narrows the result only when `type=call` (returning that contact's calls); any other `type` value is ignored and all activity types are still returned.
## PATCH /crm/activities/{id}

To update custom fields for call activities, use `"type": "call"` in the request body.

[API reference](https://developers.apideck.com/apis/crm/reference.md) · [Coverage matrix](https://developers.apideck.com/apis/crm/coverage)
