I am in need your help with a question related to associations API. How can I perform the following action:
- search for deals
- with associated Companies
- that have association label ‘lead-from-partner’
- for those deals
- set property ‘source’ to value ‘lead_from_partner’
- set property ‘source level 2’ to value ‘installer’
- for primary contact of those deals
- set property ‘source’ to ‘lead_from_installer’
- set property ‘source level 2’ to ‘installer’
I have tried both from a workflow with code custom action, and by creating a private app. The problem with the private app was giving the correct scope access level, what scope is required and how can it be enabled.
Custom code in workflow
exports.main = async (event, context) => {
try {
// ======== ENHANCED SECURITY VALIDATION ========
console.log('Initializing custom code action');
console.log('Available secrets:', Object.keys(context.secrets || {}));
// Validate secret configuration with explicit checks
const ACCESS_TOKEN = context.secrets?.PRIVATE_APP_ACCESS_TOKEN;
if (!ACCESS_TOKEN || ACCESS_TOKEN.trim() === '') {
const errorDetails = [
'403 - Invalid secret configuration',
`Secret present: ${!!ACCESS_TOKEN}`,
`Secret empty: ${ACCESS_TOKEN === ''}`,
'Required checks:',
'1. Secret name EXACTLY "PRIVATE_APP_ACCESS_TOKEN" (case-sensitive)',
'2. Value contains valid Private App token (starts with pat-)',
'3. Secret added to THIS workflow action',
'4. Private App has required scopes'
].join('\n');
throw new Error(errorDetails);
}
// ======== API CLIENT CONFIGURATION ========
const hubspotFetch = async (endpoint, options = {}) => {
console.log('Making request to:', endpoint);
const response = await fetch(`https://api.hubapi.com${endpoint}`, {
...options,
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${ACCESS_TOKEN}`,
...options.headers,
},
});
if (!response.ok) {
const errorBody = await response.text();
console.error('API request failed:', {
status: response.status,
endpoint,
errorBody
});
throw new Error(`API Error ${response.status}: ${errorBody.slice(0, 100)}`);
}
return response.json();
};
// ======== DEAL PROCESSING ========
const dealId = event.object.objectId;
console.log('Processing deal ID:', dealId);
// Get associations using v4 API
const associations = await hubspotFetch(
`/crm/v4/objects/deal/${dealId}/associations/companies?associationType=lead_from_partner`
);
console.log('Association results:', associations.results?.length || 0);
if (!associations.results?.length) {
console.warn('No companies found with "lead_from_partner" association');
return { outputFields: {} };
}
const companyId = associations.results[0].id;
console.log('Associated company ID:', companyId);
// Parallel processing for efficiency
const [company, initialUpdate] = await Promise.all([
hubspotFetch(`/crm/v3/objects/companies/${companyId}?properties=name`),
hubspotFetch(`/crm/v3/objects/deals/${dealId}`, {
method: 'PATCH',
body: JSON.stringify({
properties: {
source: 'installer',
source___level_2: 'Pending Company Name'
}
})
})
]);
console.log('Company details retrieved:', company.properties.name);
// Final property update
await hubspotFetch(`/crm/v3/objects/deals/${dealId}`, {
method: 'PATCH',
body: JSON.stringify({
properties: {
source___level_2: company.properties.name
}
})
});
console.log('Deal properties updated successfully');
return {
outputFields: {
updated_source: 'installer',
updated_source_level_2: company.properties.name
}
};
} catch (error) {
console.error('Fatal error:', {
message: error.message,
stack: error.stack,
eventObjectId: event.object?.objectId,
availableSecrets: Object.keys(context.secrets || {})
});
return {
outputFields: {},
errors: [{
message: `Custom Code Error: ${error.message}`,
category: 'CONFIGURATION_ERROR',
errorType: 'AUTHENTICATION_FAILURE'
}]
};
}
};
Any suggestions/input on how to perform the above?
