There's a lot of open issues
ruby_hubspot_api is an ORM-like wrapper for v3 of the Hubspot API
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Development

>= 11.0, < 14.0
>= 2.0, < 2.4.0
>= 2.0
>= 0.1
>= 3.0
>= 6.0
>= 3.0

Runtime

>= 0.1, < 1.0
 Project Readme

Ruby HubSpot API Gem

codecov Codacy Badge

This gem was largely inspired by hubspot-api-ruby which, in turn, was inspired by the hubspot-ruby community gem. I wanted to use version 3 of the api and simplify some parts of the interface

The Ruby HubSpot API gem is a starting point for building an ORM-like interface to HubSpot's API.

Installation

Add this line to your application's Gemfile:

gem 'ruby_hubspot_api'

And then execute:

$ bundle install

Or install it yourself as:

$ gem install ruby_hubspot_api

Configuration

To authenticate API requests, you need a HubSpot access token. First you will need to add a private app in hubspot When that is setup you can go to the "Auth" tab of your private app page and grab the access token from there

You can configure the gem by adding this code to your initializer (for Rails) or to your startup configuration (in any other environment):

Minimum configuration
Hubspot.configure do |config|
  config.access_token = 'your_access_token'
end
Full possible configuration
Hubspot.configure do |config|
  config.access_token = 'your_access_token'
  config.portal_id = 'your_portal_id'
  config.client_secret = 'your_client_secret'
  config.logger = Rails.logger
  config.log_level = 'info' # debug,info,warn,error,fatal
  config.timeout = 10 # seconds to timeout all api requests
  config.open_timeout = 5 # open_timeout seconds
  config.read_timeout = 5 # read_timeout seconds
  config.write_timeout = 5 # swrite_timeout econds (ruby >= 2.6)
end

This configuration ensures that your API requests are authenticated using your HubSpot access token.

Working with Resources

This gem allows you to interact with Hubspot resources such as contacts and companies. You can perform operations on individual resources (e.g., creating or updating records) as well as on collections (e.g., listing or searching).

please note

In the Hubspot API contacts, companies etc are referred to as "Objects" (e.g. CRM > Objects > Contacts) so when we use the word "Object" (with a capital O) we will be referring to an object in Hubspot

In this gem we use the term Resource so as not to accidentally overload Object! When we use the term "Resource" we should be referring to the ruby ORM base class and when we say "resource" we should be referring to an instance of this class (or a class that inherits it)

Hubspot::Resource class

This is the base ORM class for all Hubspot CRM objects. You should not operate on this class but with the following classes each of which inherits from Hubspot::Resource

Hubspot::Contact  # crm > contacts
Hubspot::Company  # crm > companies
Hubspot::User     # hubspot users (also referred to as 'owners')
Hubspot::Owner    # alias of Hubspot::User if you prefer to use it

however you can add custom objects of your own based on your own custom defined Objects in Hubspot

Creating and Saving an Object

Create and instance of the resource passing a hash of properties. Calling save on the instance will persist the object to the HubSpot API, as well as set the id property (which you can then store in your own database for example)

Example:

new_contact = Hubspot::Contact.new(firstname: 'John', lastname: 'Doe', email: 'john.doe@example.com')

# Save the contact to HubSpot
new_contact.save

# After saving, the contact will be assigned an ID by the API
puts "New contact ID: #{new_contact.id}"

Retreiving an Object

If you know the id of the object you can fetch it from the api using the find method

contact = Hubspot::Contact.find(1)
puts "Contact: #{contact.firstname} #{contact.lastname}"

You can also retrieve a single object by using the find_by method. Simply specify the property and the value you want to search on:

# find by email
contact = Hubspot::Contact.find_by('email', 'john.doe@example.org')
puts "Contact: #{contact.firstname} #{contact.lastname}"

#find by internal id (custom field)
contact = Hubspot::Contact.find_by('member_id', 123)
puts "Contact: #{contact.firstname} #{contact.lastname}"

Updating an Existing Object

To update an existing object, you can either modify the object and call save, or use the update method specifying the properties you want to update. You can test whether or not the object will need to upload changes to the api by using the changes? method. If you don't want to check for changes? you can use the method save! on the resource which will raise a Hubspot::NothingToDoError if there are no changes.

Example using save:

contact = Hubspot::Contact.find(1)
contact.changes? # false

contact.lastname = 'DoeUpdated'
contact.changes? # true

# save the updates to Hubspot
contact.save # true
contact.changes? # false

Example using save!:

contact = Hubspot::Contact.find(1)

MyDecorator.new(contact).apply_changes!

begin
  contact.save!
rescue Hubspot::NothingToDoError = _
  puts "Nothing changed, cancelled api call"
rescue Hubspot::RequestError => e
  puts "API Error: #{e.message}"
end

Example using update:

contact = Hubspot::Contact.find(1)
# save the updates to Hubspot
contact.update(lastname: 'DoeUpdated') # true

If you are able to construct an Object with data stored locally you can save the inital find api call, but you will need to construct the persisted object specifying the id and a properties hash (as if it came from the api!)

Example:

local_contact = Contact.find(contact_id)

hubspot_properties = {
  firstname: local_contact.first_name,
  lastname: local_contact.last_name,
  email: local_contact.email,
  # ... more properties...
}

hubspot_contact = Hubspot::Contact.new(id: contact.hs_object_id, properties: hubspot_properties )
hubspot_contact.changes? # false

# update a custom field "last_contacted"
# (in the hubspot_contact instance this will be stored in the changes property)
hubspot_contact.last_contacted = Time.now.utc.iso8601
hubspot_contact.changes? # true

# persist the change to hubspot
hubspot_contact.save #true

Listing Objects

You can list all objects (such as contacts) using the list method, which returns a PagedCollection. This collection handles paginated results and is Enumerable, so responds to methods like each_page, each, and all. You can also pass the page_size parameter to control the number of records returned per page.

Example:

contacts = Hubspot::Contact.list(page_size: 10)

# Using each_page to iterate over pages, there will be up to 10 contacts per page
contacts.each_page do |page|
  page.each do |contact|
    puts "Contact: #{contact.firstname} #{contact.lastname}"
  end
end

# Or iterate over all contacts and the pagination will be handled transparently (page_size still applies)
contacts.each do |contact|
  puts "Contact: #{contact.firstname} #{contact.lastname}"
end

# Get all contacts at once (use with caution for large datasets)
all_contacts = contacts.all

Retrieving the first n items:

As mentioned the list method returns a PagedCollection. You can call first on the result to retrieve the first item or a specified number of items:

contacts_collection = Hubspot::Contact.list

# Retrieve the first contact
first_contact = contacts_collection.first

# Retrieve the first 5 contacts
first_five_contacts = contacts_collection.first(5)

This will automatically set the limits and handle paging for the most efficient API calls while honouring the maximum page count for hubspot resources

Specifying properties

By default Hubspot will only send back the default hubspot properties

You can pass an array of properties to be returned as follows:

Example:

# Get the full list of contacts and only return specific properties
contacts = Hubspot::Contact.list(
  properties: ['firstname', 'lastname', 'email', 'mobile', 'custom_property_1']
)

contacts.each do |contact|
  puts "Name: #{contact.firstname} #{contact.lastname}, Email: #{contact.email}, Mobile: #{contact.mobile} CustomerRef: #{contact.custom_property_1}"
end

Searching

You can search for objects by passing query parameters to the search method. HubSpot supports several operators such as eq, gte, lt, and IN for filtering.

Example:

# Search for contacts with email containing "hubspot.com"
contacts = Hubspot::Contact.search({ email_contains: 'hubspot.com' }, properties: %w[firstname lastname email])

puts "Searching for Hubspot staff in the contacts CRM"
puts ""

contacts.each do |contact|
  puts "  Found: #{contact.firstname} #{contact.lastname} (#{contact.email})"
end

# Search for companies with number of employees greater than or equal to 100
puts "Searching for medium to large companies"
puts ""

companies = Hubspot::Company.search(number_of_employees_gte: 100).all # returns a PagedCollection
companies.each_page do |page_of_companies|
  page_of_companies.each do |company|
    puts "  Found: #{company.name} (#{company.number_of_employees} employees)"
  end
end

# Search for contacts with email in a specific list (IN operator)
contacts = Hubspot::Contact.search(email: ['user1@example.com', 'user2@example.com'])

contacts.each do |contact|
  puts "Found: #{contact.email}"
end

Available Search Operators:

  • contains: contains
  • neq: Not equal to.
  • gt: Greater than.
  • gte: Greater than or equal to.
  • lt: Less than.
  • lte: Less than or equal to.

Searching for empty values (NOT_HAS_PROPERTY)

Any empty value in your search will be matched using the correect filter in Hubspot

# Search for companies with no value for a given field
companies = Hubspot::Company.search({ client_category: nil }, properties: %w[name number_of_employees])
# Request body: {"filterGroups":[{"filters":[{"propertyName":"client_category","operator":"NOT_HAS_PROPERTY"}]}]

puts "Searching for uncategorised customers"
puts ""

companies.each do |company|
  category = company_category_by_size(company.number_of_employees)
  company.update(client_category: category)

  puts "  Found: #{company.name} (size #{company.number_of_employees}) - filed under #{category}"
end

Searching for any value in an array (IN)

If value is an array the operator will be set to 'IN'

# Search for companies with no value for a given field
hot_contacts = Hubspot::Contact.search(hs_lead_status: %w[IN_PROGRESS OPEN_DEAL])
# Request body: {"filterGroups":[{"filters":[{"propertyName":"hs_lead_status","operator":"IN","values":["IN_PROGRESS", "OPEN_DEAL"]}]}]

puts "Current best leads"
puts ""

hot_contacts.each do |contact|
  puts "#{contact.name} .... "
end

Chaining search conditions (a la Active Record)

You can use the alternative Rails-like interface to return a PagedCollection and can chain calls. Use where(search_params) to filter and .select('firstname', 'lastname', 'email', 'custom_property') to determine the properties returned by the API.

contact = Hubspot::Contact.where(hs_lead_status: [])

Getting the total number of records

Having created a search collection you can retrieve the total number of matching records from Hubspot by calling .total on the collection. This will make a single request to the api to retrieve the number of matching records

contacts_collection = Hubspot::Contact.search(lead_status: 'cold')

if contacts_collection.total > 200
  puts "Contacts will be processed overnight"
  # schedule_processing_job
else
  puts "Found #{contacts_collection.total} contacts. Processing...."
  process_contacts(contacts_collection)

Specifying Properties in Search

When performing a search, you can also specify which properties to return. NB If you specify any properties, you will only get those properties back, and the default HubSpot properties will not be included automatically.

Example:

# Search for contacts with email containing "hubspot.com" and only return specific properties
contacts = Hubspot::Contact.search(
  { email_contains: 'hubspot.com' },
  properties: ['firstname', 'lastname', 'email', 'mobile', 'custom_property_1']
)

contacts.each do |contact|
  puts "Name: #{contact.firstname} #{contact.lastname}, Email: #{contact.email}, Mobile: #{contact.mobile} CustomerRef: #{contact.custom_property_1}"
end

Working with batches

Hubspot::Batch

The Hubspot::Batch class allows you to perform batch operations on HubSpot resources, such as contacts or companies. This includes batch create, read, update, upsert, and archive operations. Below are examples of how to use these methods.

Batch Create

To create new resources in bulk, you can use the create method.

In this example, batch.create triggers the creation of new contacts. After creation, the batch response will include the new IDs assigned to each object by HubSpot which will be assigned to the resources in the batch

contacts = [
  Hubspot::Contact.new(email: 'new.john@example.com', firstname: 'John', lastname: 'Doe'),
  Hubspot::Contact.new(email: 'new.jane@example.com', firstname: 'Jane', lastname: 'Doe')
]

batch = Hubspot::Batch.new(contacts)
batch.create

batch.resources.each do |contact|
  hubspot_id = contact.id
  # store hubspot_id against a contact....
end

Batch Read

To read a batch of Objects by their internal hubspot id or by another uniq property you can use the read method. You will need to pass the class of the resource, an array of ids and optionally an id_property.

For simplicity you can also use the batch_read method of the corresponding class (e.g. Hubspot::Contacts, Hubspot::Company etc) passing an array of ids and optionally an id_property (defaults to 'id'). This method will return a Hubspot::Batch and the results will be in "resources"

Example using read along with the Hubspot id of several companies...

# Grab an array of hubspot company ids to read from the api...
company_ids = my_companies.collect(&:hubspot_id).compact

# this will grab all the results from the api handling paging automagically
batch = Hubspot::Batch.read(Hubspot::Company, company_ids)
companies = batch.resources

# Or using the domain field
# Grab an array of company domains
company_domains = my_companies.collect(&:domain_name).compact

# calls /crm/v3/objects/companies/batch/read
batch = Hubspot::Batch.read(Hubspot::Company, company_domains, id_property: 'domain')
companies = batch.resources

Example of reading contacts by email and the helper method batch_read By using this method you can page through the results as needed or collect them

email_addresses = my_selected_contacts.collect(&:email).compact

batch = Hubspot::Contact.batch_read(email_addresses, id_property: 'email')

batch.each_page do |contacts|
  contacts.each do |contact|
    # persist some data locally
    update_local_contact_from_hubspot(contact)
  end
  # stop the api calls if a condition is met
  # break if <condition>
end

Finally there is another helper method batch_read_all on any Hubspot::Resource class (Hubspot::Contact, Hubspot::Company, Hubspot::User etc) which will read all of the resources and return a HubSpot::Batch (with all of the resources).

You can then update the resources and call update on the batch.... see below

Batch Update

For updating existing resources in bulk, you can use the update method. If you want to locally create an object without calling the API you specify the id and pass any properties in the 'properties' hash (this is how the objects are returned from Hubspot)

contacts = [
  Hubspot::Contact.new(id: 1, properties: { firstname: 'John', lastname: 'Doe' }),
  Hubspot::Contact.new(id: 2, properties: { firstname: 'Jane', lastname: 'Doe' })
]

# make a changes to each contact
contacts.each { |contact| contact.last_contacted = Time.now.utc.iso8601 }

batch = Hubspot::Batch.new(contacts)
batch.update

Example using a batch

user_ids = my_selected_users.collect(&:hubspot_id).compact
batch = Hubspot::User.batch_read(user_ids)

batch.resources.each do |hubspot_user|
  # some logic or method to set any new/changed properties on hubspot_user
  hubspot_user.sales_total = fetch_sales_total(user.email)
end

# now we have a batch with changed resources we can update the batch
batch.update # true

Batch Upsert

The upsert method allows you to insert new records or update existing ones. You’ll need to specify an id_property (like email) to uniquely identify records

contacts = [
  Hubspot::Contact.new(email: 'new.john@example.com', firstname: 'John', lastname: 'Doe'),
  Hubspot::Contact.new(email: 'new.jane@example.com', firstname: 'Jane', lastname: 'Doe')
]

batch = Hubspot::Batch.new(contacts, id_property: 'email')
batch.upsert

In this example, if a contact with the given email already exists in HubSpot, it will be updated. If it doesn't, a new contact will be created.

Batch Archive

To archive objects in bulk, you can use the archive method. This removes the objects from HubSpot.

contacts = Hubspot::Contact.where(email_contains: 'hubspot.com').all

batch = Hubspot::Batch.new(contacts)
batch.archive

The archive method sends a batch request to HubSpot to archive the objects. If any of the objects fail to be archived, you can check for partial success using the partial_success? method.

Error Handling and Success Checks

You can check whether the batch operation was entirely successful, partially successful, or if any failures occurred:

if batch.all_successful?
  puts "All resources were successfully processed."
elsif batch.partial_success?
  puts "Some resources were successfully processed, but others failed."
else
  puts "The batch operation failed."
end

Custom Resources

If you have defined custom objects you can easily add them by creating a class that inherits from Hubspot::Resource

# lib/hubspot/projects.rb

require 'ruby_hubspot_api' # if not required by bundler already...

module Hubspot
  class Project < Resource

    # resource_name (part of the url in the api) will default
    # to a simple plural of the class name - in this case 'projects'
    # if the url for your custom object is different you can override it

    def resource_name
      'company_projects'
    end
  end
end

projects = Hubspot::Projects.where(status: ['upcoming', 'active', 'overrun']).all

Contributing

There is much to do (including writing a TODO list, or at least adding issues in github!) but this should provide a solid start

If you're interested in contributing to the gem, follow the instructions below.

Developer Setup

A .env.sample file is provided with all the environment variables needed for development, including testing credentials. To set up your environment:

  1. Copy the .env.sample file to .env.
  2. Update the values with your own credentials (e.g., your HUBSPOT_ACCESS_TOKEN).

The .env file will be used to configure your access to the HubSpot API and ensure that environment variables are properly loaded during development.

Using VCR for Testing

We use VCR for recording and replaying HTTP requests during testing. You can control the VCR recording mode by setting the VCR_RECORD_MODE environment variable as a string. The following modes are supported:

  • none (default): No new requests are recorded; only replay from existing cassettes.
  • all: Records all requests, overwriting existing cassettes.
  • new_episodes: Records new requests, but keeps existing cassettes unchanged.
  • once: Records the first time tests are run, and replays on subsequent runs.

You can specify the VCR_RECORD_MODE either via the command line or in the .env file (see the .env.sample file for examples).

To change the record mode on the command line, use:

$ export VCR_RECORD_MODE=all

For more information on how VCR modes work, refer to the VCR documentation.

Running Tests

To run the tests, simply execute:

$ rspec

Ensure you have your .env file configured with a valid HUBSPOT_ACCESS_TOKEN for API integration tests if you want to rerecord your interactions.

License

The gem is available as open source under the terms of the MIT License.