Gotchas with the HubSpot API

One of my first projects I worked on at Mantle was building an integration with HubSpot. HubSpot is basically a powerful CRM for sales, marketing, support, and several other aspects of running a business. The main goal of the integration comprised Mantle customers being able to push their data into their HubSpot instance. This was a common request from several customers as Mantle is a rich datasource and HubSpot is where a lot of their email marketing, sales, and reporting was done.

As I had no past experience integrating with the HubSpot API, this was all new to me and took longer than expected to figure out how to develop on their platform. Beyond the HubSpot API docs being hard to comprehend, there was a few gotchas I experienced while building out the functionality needed to push customer data into HubSpot. Here’s several notable gotchas I ran into.

Which HubSpot account am I connected to?

I needed to know which HubSpot account was actually connected to after the user performed the oAuth connection. This was quite useful to know since I had a mapping of Mantle customers to HubSpot contacts, and reconnecting HubSpot with a different account could potentially mess up data in HubSpot.

The /account/info api endpoint does have the portalId field available, which is just an integer representing the account, but otherwise no nice name field exists there. Instead it’s possible to get the HubSpot account name from the oauth access or refresh token inspection api endpoint. The field is called hub_domain. hub_id (the same as portalId) is also available on this endpoint, so no need to call the /account/info endpoint.

Right after a successful oauth connection, I perform a request to the token inspection API to fetch these values.

Optional api scopes

Some api scopes are only available on some plans. There’s no way to check which plan a person is on - probably because the features can be pick-and-choose. If you’re using scopes that aren’t available on all plans then you should put them in optional_scopes if the app should still work without them. Hubspot has a crappy, unhelpful message whenever an app requires a scope that the org doesn’t have that is displayed during the oauth flow.

Dealing with custom field dropdown/enum values

Many systems implement some form of a field having a known set of values that can be set. HubSpot has this field-type available for checkboxes, radio buttons, and dropdowns. They’re all powered by their datatype called enumerable.

Say you want to provide a user within HubSpot the ability to easily create a report or list of customers based on the data you push into HubSpot. Being able to use a dropdown, checkboxes, or radio buttons provides a much easier experience over manually typing in values into a text box. This better reality is a pain to accomplish given you need to tell HubSpot all of the known possible values up-front when first creating the field, and then later on whenever the values may change. If its a static set of known values that won’t ever change, perfect, you’re good to use this great feature. If the values are user-created or can change over time, you’re in trouble. HubSpot won’t calculate the known values by itself, so you’re forced to keep track of the state of HubSpot’s known values for that field are, or avoid the extra complexity and just stick with a text field.

Modelling data in custom fields

Its great to be able to create custom fields with different datatypes, but when it comes to storing a set of tuples? ie. many key-values. How can you do it, and what’s most useable in HubSpot? As an example, we want to store data about which apps a user has and their respective plans. The example uses two apps, each with their own plan name.

Option 1: Use a single text field and separate multiple values with a newline or some other character

Eg. Field name: App plan name Value: App 1 - Pro\n App 2 - Custom


  • Conservative use of fields by only using one
  • Very compact display if shown on the contact’s page


  • When using the field in reports, workflows, etc. would have to use a contains operator and then match on the string App 2 - Custom, or multiple combinations of that for multiple types of values
  • Always have to use text type

Option 2: Use multiple fields for each app, and store the value in each

This method gives you the flexibility of giving each key its own field to store whichever values it needs. An enumerable could be used for the values, and this would result in quite easy report building and searching abilities since each field is a distinct app and all of the potential values are present from the HubSpot interface.

There does come some downsides such as needing to be cognizant of there being an account-wide 1000 custom field limit, and if you decide to use the enumerable type, keeping the potential values in sync as they may change over time. But we know that can be more of a chore than practical.

Eg. Field name 1: app 1 plan name Value 1: Pro Field name 2: app 2 plan name Value 2: Custom


  • One field to easily match on with the equals operator
  • Potential for using dropdown/enum field


  • Could reach the 1000 custom field limit if there’s many keys

Option 3: HubSpot custom objects

If you’re on the HubSpot enterprise plan, you’ve got the ability to use custom objects. They’re supposedly a way to model any sort of data within HubSpot and connect it to HubSpot’s existing objects like Contacts, Companies, etc.

I haven’t explored this option too deeply as the requirement for the Enterprise plan was a deal breaker for the variety of customers I was building this integration for.

Picking one

Whichever way you go, pick a key that won’t change. With the app name example, that can probably change over time, so if there’s a slug, or non-modifiable field, go with that.

I ended up going with option 1, using a single field to hold all keys and values, since this was the lowest common denominator where the custom field use was conservative. It still offered the ability to use these values in reports, but with a higher level of work compared to option 2: having a custom field for each key.

Querying contacts by email

Most surprising of all, I was querying for a contact record with one specific email and was getting back a record with a different email. This was highly confusing and was only happening with one of our customers, and for one of their contacts.

For example, search for a contact with an email of [email protected], and get back a contact with their email saying its [email protected]. This ended up being a “feature” of HubSpot where contacts can have many secondary emails attached, and when searching for a contact by email, these secondary emails are also searched. Because I needed to confirm the returned contact’s email, I needed to modify my query to also return all the secondary emails.

The docs absolutely suck for telling you about it. From my investigation only the forums and old docs site have a reference to it. The field is called secondaryEmails and can be included in the returned properties when querying contacts.

Multiple doc sites, and just docs in general

This last one is just laziness. The old documentation site still lurks around and often shows up high in the search results. The API docs there are often for a prior version of the API, or just contain even less info than the corresponding page on the current docs site. Why not kill this old version already?

The new docs site is also pretty crappy. Knowing what the right inputs are and what the output schema could be is a matter of putting together several code samples and using the sad iframed API endpoint explorer. Please bring your documentation platform into this decade by specifying all the potential inputs and outputs to your APIs, and provide some better UX.

As I’m writing this, HubSpot now has a beta of their new documentation site. The UX is a bit more modern, viewing API examples are easier, and interacting with the API looks better. Good job, HubSpot.

That’s all of the troubles I’ve run into so far while developing an integration for HubSpot. I hope these serve as useful pointers for others as they build their own integrations, or at least for myself when I have to go add something new to this integration.