HubSpot’s v4 Associations API lets you link CRM records together — connecting a deal to a company, a contact to a deal, or a deal to its line items. The API is more powerful than v3 (it supports labeled associations), but the docs can be confusing. This guide covers the key concepts, the correct association type IDs, and working Python examples you can use right away.
What Are Associations in HubSpot?
Associations are links between two CRM records. They define how objects relate to each other. For example:
- A contact works at a company
- A deal belongs to a company
- A deal includes one or more line items
Without associations, these records exist in isolation. The v4 API is how you create and manage these links programmatically.
How the v4 Associations API Works
To create an association, you send a PUT request with three pieces of information:
- Source and target objects — the two record types and their IDs (specified in the URL)
- associationCategory — either
HUBSPOT_DEFINED(default labels) orUSER_DEFINED(custom labels you created) - associationTypeId — a numeric ID that defines the relationship type and direction
The endpoint format is:
PUT /crm/v4/objects/{fromObjectType}/{fromObjectId}/associations/{toObjectType}/{toObjectId}
The request body is a JSON array with the association category and type ID:
[
{
"associationCategory": "HUBSPOT_DEFINED",
"associationTypeId": 5
}
]
Common Association Type IDs
Association type IDs are directional — “contact to company” has a different ID than “company to contact”. Getting the direction wrong is the most common mistake. Here are the default HubSpot-defined IDs you’ll use most often:
| Association | Type ID |
|---|---|
| Contact to Company (Primary) | 1 |
| Company to Contact (Primary) | 2 |
| Deal to Contact | 3 |
| Contact to Deal | 4 |
| Deal to Company | 5 |
| Company to Deal | 6 |
| Deal to Line Item | 19 |
| Line Item to Deal | 20 |
The full list is in HubSpot’s official v4 Associations documentation. For custom objects, the IDs will be different — use the labels endpoint (covered below) to look them up.
Look Up Association Type IDs Dynamically
Instead of hardcoding type IDs, you can query HubSpot for the available associations between any two object types:
GET /crm/v4/associations/{fromObjectType}/{toObjectType}/labels
In Python:
import requests
url = "https://api.hubapi.com/crm/v4/associations/deals/companies/labels"
headers = {"Authorization": "Bearer YOUR_ACCESS_TOKEN"}
response = requests.get(url, headers=headers)
print(response.json())
This returns every association label and its typeId for that object pair. It’s especially useful for custom objects or when you’ve created custom association labels in your portal.
Python Example: Associate a Deal with a Line Item
Here’s a working example that links a deal to a line item using the v4 API:
import requests
import json
deal_id = "38700000000"
line_item_id = "34400000000"
url = (
f"https://api.hubapi.com/crm/v4/objects/deals/{deal_id}"
f"/associations/line_items/{line_item_id}"
)
payload = json.dumps([
{
"associationCategory": "HUBSPOT_DEFINED",
"associationTypeId": 19
}
])
headers = {
"Content-Type": "application/json",
"Authorization": "Bearer YOUR_ACCESS_TOKEN"
}
response = requests.put(url, headers=headers, data=payload)
print(f"Status: {response.status_code}")
print(f"Response: {response.text}")
Replace deal_id, line_item_id, and YOUR_ACCESS_TOKEN with your actual values. The access token needs CRM object write permissions.
Testing with Postman
If you want to test before writing code:
- Set method to PUT
- URL:
https://api.hubapi.com/crm/v4/objects/deals/{dealId}/associations/line_items/{lineItemId} - Add header:
Authorization: Bearer YOUR_ACCESS_TOKEN - Add header:
Content-Type: application/json - Set body to raw JSON:
[
{
"associationCategory": "HUBSPOT_DEFINED",
"associationTypeId": 19
}
]
A 200 response means the association was created. If you get a 400, double-check that the type ID matches the direction of your source and target objects.
Associating Multiple Objects at Once
In a real integration, you often need to link several objects together — for example, when syncing a new deal that should be connected to a company, a contact, and its line items. Here’s a reusable function that handles that:
import requests
import json
HUBSPOT_API_URL = "https://api.hubapi.com"
ACCESS_TOKEN = "YOUR_ACCESS_TOKEN"
ASSOCIATION_TYPES = {
("companies", "contacts"): 2,
("contacts", "deals"): 4,
("deals", "companies"): 5,
("deals", "line_items"): 19,
}
def create_association(from_type, from_id, to_type, to_id):
type_id = ASSOCIATION_TYPES.get((from_type, to_type))
if type_id is None:
print(f"No type ID mapped for {from_type} -> {to_type}")
return None
url = (
f"{HUBSPOT_API_URL}/crm/v4/objects/{from_type}/{from_id}"
f"/associations/{to_type}/{to_id}"
)
payload = json.dumps([
{
"associationCategory": "HUBSPOT_DEFINED",
"associationTypeId": type_id
}
])
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {ACCESS_TOKEN}"
}
response = requests.put(url, headers=headers, data=payload)
print(f"{from_type}/{from_id} -> {to_type}/{to_id}: {response.status_code}")
return response
def associate_deal_records(company_id, contact_id, deal_id, line_item_id):
"""Link a deal to its related company, contact, and line item."""
if company_id and contact_id:
create_association("companies", company_id, "contacts", contact_id)
if contact_id and deal_id:
create_association("contacts", contact_id, "deals", deal_id)
if deal_id and company_id:
create_association("deals", deal_id, "companies", company_id)
if deal_id and line_item_id:
create_association("deals", deal_id, "line_items", line_item_id)
The ASSOCIATION_TYPES dictionary maps each object pair to its correct type ID, so you only need to maintain IDs in one place. If you’re running this inside Lambda, consider adding retry logic for HubSpot API requests to handle rate limits and transient errors.
Tips from Working with v4 Associations
- Direction matters. “Deal to Company” (type ID 5) is not the same as “Company to Deal” (type ID 6). Always match the direction in your URL to the type ID.
- Log your responses. The API returns helpful error messages. Print
response.textduring development — it tells you exactly what went wrong. - Use a lookup map. Keep your type IDs in a dictionary (like the
ASSOCIATION_TYPESexample above) instead of scattering magic numbers throughout your code. - Query the labels endpoint first if you’re working with custom objects or custom labels. The default IDs in the table above only cover standard HubSpot objects.
Conclusion
The v4 Associations API is straightforward once you know the correct type IDs and understand that direction matters. Use the labels endpoint to discover IDs dynamically, keep a mapping dictionary in your code, and always log API responses during development.
If you’re building a Lambda-based integration that syncs HubSpot data, check out Sync HubSpot Company Records to S3 Using AWS Lambda and Step Functions. For handling large batch operations without timeouts, see How to Avoid AWS Lambda Timeout When Processing HubSpot Records.