A faster way to get contacts associated with deals associated to a company

The structure I’ve inherited is a company has a deal for each contact.

This was done to describe the relationship for the contact to the company. The contact could be in multiple companies but that relationship could be different. Full time employee for one company, half time for another company for example.

Currently I’ve written code to first get all the deals in a given company using the /crm/v3/objects/deals/search end point (super annoying this isn’t in the php API on GitHub btw..). Uses 3 filters and returns 25 results in ~0.4s. No problem.

Then I iterate over those 25 deal results using /crm/v3/objects/contacts/search endpoint with a filter of associations.deal EQ dealID to get contacts properites like firstname etc and merge the contact properities with the deal ones that described the relationship.

This takes around 13 seconds for 25 contacts.

How can I speed that up? Obviously I’m having to hit the API 26 times in this example.

When getting the deals, I’ve tried to get the associated contact properties there (as it is a 1:1) with no luck.

Hey @LanceH , how are you doing with this?

I have read this question about 75 times and am still not sure what to do with it :upside_down_face:

I want to make sure that I am understanding the question properly and will reduce 25 deals to 1 to hopefully make it a simpler starting point

  1. use crm search on deals to get deal for a company (what are the 3 filters?)
  2. use crm search on contacts to get contact associated with deal from step 1
  3. within the results of the contact search, return specific contact properties that you want to sync with deal properties
  4. (this one wasn’t in your steps you listed, but is assumed) take the contact properties in step 3 and patch the deal with the properties

Does this seem correct?

@zaklein , @Mike_Eastwood either of you have some ideas on how to tackle this?

Hi @dennisedson

Havn’t got the current code any quicker. Started to change tact and looked at storing a local copy of the data in the site and using cron to sync it. Not ideal from a maintenace POV.

Your summary sounds correct. It was done (in this cumbersome way) to get around the limitation of HS not allowing contacts to be in multiple companies. I see that is in beta but I’m not sure those associations will allow for “association specific properties” though.

Your step 4 is correct. It was this bit: “merge the contact properities with the deal ones”.

The posted settings sent to the deals endpoint is this:

$post = [
	'filterGroups' => [[
		'filters' => [
			[
				'propertyName' => 'associations.company',
				'operator' => 'EQ',
				'value' => $companyID
			],
			[
				'propertyName' => 'pipeline',
				'operator' => 'EQ',
				'value' => '2495083'
			],
			[
				'propertyName' => 'closedate',
				'operator' => 'NOT_HAS_PROPERTY'
			]
		]
	]],
	'properties' => [
		'practice_fte_deal_',
		'employment_start_date',
		'employment_left_date',
		'firstname',
		'dealname'
	],
	'limit' => '100'
];

$deals = $this->cURL('POST', 'https://api.hubapi.com/crm/v3/objects/deals/search', $post); 

That’s the quick part around 0.4s.

Those deal results are looped and this is the code for getting the contact out of the deal:

$post = [
	'filterGroups' => [[
		'filters' => [
			[
				'propertyName' => 'associations.deal',
				'operator' => 'EQ',
				'value' => '' # set inside the loop
			]
		]
	]],
	'properties' => [
		'firstname',
		'lastname',
		'type_of_employee',
		# fields removed for brevity
	],
	'limit' => '100',
	'sorts' => [[
		'propertyName' => 'firstname',
		'direction' => 'ASCENDING'
	]]
];

foreach ($deals->results as $deal)
{ 
	$post['filterGroups'][0]['filters'][0]['value'] = $deal->id;

	$contacts = $this->cURL('POST', 'https://api.hubapi.com/crm/v3/objects/contacts/search', $post);

	if (isset($contacts->results))
	{
		foreach ($contacts->results as $contact)
		{
			$fields = [];

			# Contact fields
			$fields['HPI'] 	= $contact->properties->hpi_number;
			# fields removed for brevity

			# Deal fields
			$fields['FTE'] = $deal->properties->practice_fte_deal_;
			# fields removed for brevity
		}

		$staff[$fields['HubSpotID']] = $fields;
	}
}

return $staff;

By the end of this, the data is all the contacts (deal and contact properties merged) that have a deal in a given company.

There is 4926 contacts in total in this HS instance. I’m not sure where the latency come in. Just the overhead of the API round trip, some kind of applied rate limit to many requests close together or is searching the contacts by a deal id not efficient.

Putting some simple timing around the calls - they look like this:

Deals took 0.53 seconds.
This contact took 0.47 seconds.
This contact took 0.48 seconds.
This contact took 0.54 seconds.
This contact took 0.51 seconds.
This contact took 0.47 seconds.
This contact took 0.56 seconds.
This contact took 0.61 seconds.
This contact took 0.62 seconds.
This contact took 0.51 seconds.
This contact took 0.62 seconds.
This contact took 0.52 seconds.
This contact took 0.61 seconds.
This contact took 0.41 seconds.
This contact took 0.72 seconds.
This contact took 0.46 seconds.
This contact took 0.57 seconds.
This contact took 0.46 seconds.
This contact took 0.47 seconds.
This contact took 0.51 seconds.
This contact took 0.51 seconds.
This contact took 0.51 seconds.
This contact took 0.51 seconds.
This contact took 0.42 seconds.
This contact took 0.50 seconds.
This contact took 0.49 seconds.
Whole thing took 13.58 seconds.

Hi @LanceH

To improve efficiency in this case, I think you’ll need to avoid the use of single operations. Have you considered using HubSpot’s CRM batch operation endpoints to get this job done?

For example:

  1. CRM Search Deals (as per your example, no change to this step)
  2. Read a batch of associations from Deals to Contacts (where Deal IDs returned from step 1 are submitted in the request JSON body)
    • POST /crm/v3/associations/deal/contact/batch/read
  3. Read a batch of contacts (where Contact IDs returned from step 2 and target Contact properties to be returned are submitted in the request JSON body)
    • POST /crm/v3/objects/contact/batch/read
  4. Update a batch of deals (where Deal IDs from step 1 are submitted in the request JSON body along with their target property values)
    • POST /crm/v3/objects/deal/batch/update

One of the main complexities in this flow is keeping the relationship mappings clear between associated Contact and Deal records -- obviously, there are a number of ways this could be handled, but you could always use the response from step 2 to help with this.

Please let me know if you have any follow up questions. I hope this proves helpful :slightly_smiling_face:

All the best,

Zach