How to Use HubSpot v4 Associations API in Python

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:

  1. Source and target objects — the two record types and their IDs (specified in the URL)
  2. associationCategory — either HUBSPOT_DEFINED (default labels) or USER_DEFINED (custom labels you created)
  3. 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:

  1. Set method to PUT
  2. URL: https://api.hubapi.com/crm/v4/objects/deals/{dealId}/associations/line_items/{lineItemId}
  3. Add header: Authorization: Bearer YOUR_ACCESS_TOKEN
  4. Add header: Content-Type: application/json
  5. 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.text during development — it tells you exactly what went wrong.
  • Use a lookup map. Keep your type IDs in a dictionary (like the ASSOCIATION_TYPES example 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.