Chapter 2 Β Β·Β CRM API Complete Guide
CRM API
complete guide
Overview and implementation patterns of Objects API, Search API, Associations API, and Batch API.
You will systematically learn how to utilize CRM APIs at a practical level, including workarounds for rate limits, pagination, and processing of large amounts of data.
2-1 Overview of CRM API
HubSpot CRM API v3 is an Object-based RESTful API. First, understand the structure.
π Basic structure of CRM API
The HubSpot CRM API isObjectIt is designed in units.
In addition to standard objects such as Contacts, Companies, Deals, and Tickets, custom objects can also be manipulated using the same API pattern.
Each object has properties, associations, and engagements associated with it.
| object | API path | Purpose |
| Contacts | /crm/v3/objects/contacts | Personal information of prospective customers/customers |
| Companies | /crm/v3/objects/companies | Business partner information |
| Deals | /crm/v3/objects/deals | Business negotiations/proposals |
| Tickets | /crm/v3/objects/tickets | support ticket |
| Products | /crm/v3/objects/products | Products/Product Catalog |
| Line Items | /crm/v3/objects/line_items | Lines associated with opportunities |
| Quotes | /crm/v3/objects/quotes | Estimate |
| Custom Objects | /crm/v3/objects/{objectType} | Custom objects (detailed in Chapter 4) |
Base URL:
The base URL for all CRM API requests is https://api.hubapi.com is.
The authentication header is Authorization: Bearer {ACCESS_TOKEN} use.
2-2 Objects API β CRUD operations
Master the basic operations of creating, retrieving, updating, and deleting contacts using contacts as an example.
Contacts endpoint list
GET
/crm/v3/objects/contacts
Get contact list (pagination supported)
GET
/crm/v3/objects/contacts/{contactId}
Get specific contact by ID
POST
/crm/v3/objects/contacts
Create new contact
PATCH
/crm/v3/objects/contacts/{contactId}
Update contact properties
DELETE
/crm/v3/objects/contacts/{contactId}
Archive contacts (logical delete)
Node.js β Contact CRUD
import { Client }
from '@hubspot/api-client';
const client =
new Client({ accessToken: process.env.HUBSPOT_ACCESS_TOKEN });
const newContact =
await client.crm.contacts.basicApi.create({
properties: {
email:
'taro@example.com',
firstname:
'Taro',
lastname:
'Yamada',
phone:
'090-1234-5678',
company:
'Sample Co., Ltd.',
},
});
console.log(
'Created:', newContact.id);
const contact =
await client.crm.contacts.basicApi.getById(
newContact.id,
[
'email',
'firstname',
'lastname',
'hs_lead_status']
);
await client.crm.contacts.basicApi.update(newContact.id, {
properties: {
hs_lead_status:
'IN_PROGRESS',
lifecyclestage:
'opportunity',
},
});
await client.crm.contacts.basicApi.archive(newContact.id);
Node.js β List acquisition and pagination
async function getAllContacts() {
const allContacts = [];
let after = undefined;
do {
const page =
await client.crm.contacts.basicApi.getPage(
100,
after,
[
'email',
'firstname',
'lastname',
'createdate']
);
allContacts.push(...page.results);
after = page.paging?.next?.after;
if (after)
await new Promise(r => setTimeout(r,
100));
}
while (after);
return allContacts;
}
Archive vs Permanent Delete:
archive() is a logical delete (archive) and can be restored for 30 days.
If complete deletion is required (GDPR compliance etc.) gdprDeleteApi.purge() but useUnrecoverablePlease be careful.
2-3 Search API β Advanced search refinement
Search CRM data flexibly by combining filtering, sorting, and full-text search.
π Features of Search API
Search API POST /crm/v3/objects/{objectType}/search At the endpoint,
multipleFilter group (OR condition)andFilter (AND condition)It is possible to perform complex narrowing down by combining.
Maximum in 1 request 10,000 itemsIt also supports pagination.
| operator | meaning | example |
EQ | equal | lifecyclestage = "customer" |
NEQ | not equal | hs_lead_status β "UNQUALIFIED" |
GT / GTE | greater than/more than | createdate > specific date and time |
LT / LTE | less than/less than | amount < 100000 |
CONTAINS_TOKEN | Contains token (partial match) | email contains "@example.com" |
IN | any in the list | country IN ["JP", "US", "UK"] |
NOT_IN | none in list | lifecyclestage NOT IN ["other"] |
HAS_PROPERTY | property exists | phone is set |
NOT_HAS_PROPERTY | property does not exist | email not set |
BETWEEN | within range | amount BETWEEN 10000 AND 50000 |
Node.js β Search API implementation example
const thirtyDaysAgo = Date.now() - (
30 *
24 *
60 *
60 *
1000);
const searchResult =
await client.crm.contacts.searchApi.doSearch({
filterGroups: [
{
filters: [
{
propertyName:
'lifecyclestage',
operator:
'EQ',
value:
'customer',
},
{
propertyName:
'createdate',
operator:
'GTE',
value: thirtyDaysAgo.toString(),
},
],
},
],
properties: [
'email',
'firstname',
'lastname',
'createdate'],
sorts: [{ propertyName:
'createdate', direction:
'DESCENDING' }],
limit:
100,
after:
0,
});
console.log(
`Total: ${searchResult.total}`);
console.log(searchResult.results);
Node.js β OR condition (multiple filter groups)
const result =
await client.crm.contacts.searchApi.doSearch({
filterGroups: [
{
filters: [{ propertyName:
'lifecyclestage', operator:
'EQ', value:
'lead' }],
},
{
filters: [{ propertyName:
'lifecyclestage', operator:
'EQ', value:
'opportunity' }],
},
],
properties: [
'email',
'lifecyclestage',
'hubspot_owner_id'],
limit:
200,
});
Node.js β Get all results using Search API (pagination)
async function searchAll(objectType, filterGroups, properties = []) {
const results = [];
let after =
0;
while (true) {
const page =
await client.crm[objectType].searchApi.doSearch({
filterGroups,
properties,
limit:
100,
after,
});
results.push(...page.results);
if (!page.paging?.next?.after)
break;
after = page.paging.next.after;
await new Promise(r => setTimeout(r,
200));
}
return results;
}
const allDeals =
await searchAll(
'deals', [
{ filters: [{ propertyName:
'dealstage', operator:
'EQ', value:
'closedwon' }] },
], [
'dealname',
'amount',
'closedate']);
β Search API limits:
The maximum number of records that can be retrieved in one search is 10,000 itemsis. If you want to retrieve data for more than 10,000 items,
Either split the data by date range etc. and request it multiple times, orExports API Please consider using.
2-4 Associations API β Associations between objects
Connect objects such as contacts, companies, and deals with each other to express CRM relationships.
π Basic concepts of Associations
HubSpot manages relationships between different objects as "associations."
For example, typical relationships include "Contact β Company," "Contact β Business Negotiation," and "Business Negotiation β Company."
In v3 API association type By specifying , you can also express the type of relationship (main person in charge, stakeholder, decision maker, etc.).
| Frequently used associations | fromObject | toObject | typeId |
| Contact β Company (main affiliation) | contacts | companies | 1 |
| Company β Contact | companies | contacts | 2 |
| Contact β Business negotiation | contacts | deals | 4 |
| Business β Contact | deals | contacts | 3 |
| Company β Business negotiation | companies | deals | 6 |
| Business negotiation β Company | deals | companies | 5 |
| Contact β Ticket | contacts | tickets | 16 |
Node.js β Associations CRUD
await client.crm.associations.v4.basicApi.create(
'contacts',
contactId,
'companies',
companyId,
[{ associationCategory:
'HUBSPOT_DEFINED', associationTypeId:
1 }]
);
const associations =
await client.crm.associations.v4.basicApi.getPage(
'contacts',
contactId,
'companies'
);
console.log(associations.results.map(a => a.toObjectId));
await client.crm.associations.v4.basicApi.archive(
'contacts', contactId,
'companies', companyId
);
Node.js β Object creation and association at the same time
const newDeal =
await client.crm.deals.basicApi.create({
properties: {
dealname:
'Enterprise Agreement 2026',
amount:
'5000000',
dealstage:
'appointmentscheduled',
pipeline:
'default',
closedate:
'2026-06-30',
},
associations: [
{
to: { id: contactId },
types: [{ associationCategory:
'HUBSPOT_DEFINED', associationTypeId:
3 }],
},
{
to: { id: companyId },
types: [{ associationCategory:
'HUBSPOT_DEFINED', associationTypeId:
5 }],
},
],
});
2-5γBatch API β Efficient processing of large amounts of data
Manipulate multiple records at once in a single request, reducing the number of API calls and the load on rate limits.
β‘ Features of Batch API
Batch API is maxed out in 1 request 100 itemsRecords can be operated on at once.
It is up to 100 times more efficient than operating one item at a time, and the rate limit (100req/10sec) can be significantly relaxed.
All operations such as create, read, update, and delete are supported.
Node.js β Batch creation
const batchCreateResult =
await client.crm.contacts.batchApi.create({
inputs: [
{ properties: { email:
'user1@example.com', firstname:
'User 1' } },
{ properties: { email:
'user2@example.com', firstname:
'User 2' } },
{ properties: { email:
'user3@example.com', firstname:
'User 3' } },
],
});
console.log(
`Created ${batchCreateResult.results.length} contacts`);
Node.js β Batch updates
const batchUpdateResult =
await client.crm.contacts.batchApi.update({
inputs: [
{ id:
'101', properties: { lifecyclestage:
'customer' } },
{ id:
'102', properties: { lifecyclestage:
'customer' } },
{ id:
'103', properties: { hs_lead_status:
'UNQUALIFIED' } },
],
});
Node.js β Utilities for processing large amounts of data in Batch
async function processBatch(items, batchFn, chunkSize =
100) {
const results = [];
for (
let i =
0; i < items.length; i += chunkSize) {
const chunk = items.slice(i, i + chunkSize);
const result =
await batchFn(chunk);
results.push(...result.results);
if (i + chunkSize < items.length) {
await new Promise(r => setTimeout(r,
200));
}
}
return results;
}
const updates = contactIds.map(id => ({
id,
properties: { custom_sync_status:
'synced' },
}));
await processBatch(updates, (chunk) =>
client.crm.contacts.batchApi.update({ inputs: chunk })
);
2-6 Properties API β Manage custom properties
Manage property list acquisition, creation, and update using API.
| property type | fieldType | Purpose |
| string | text / textarea | text input |
| number | number | Numerical value (amount, score, etc.) |
| enumeration | select / radio / checkbox | Multiple choice. requires options list |
| date | date | Date (YYYY-MM-DD) |
| datetime | date | Date and time (Unix milliseconds) |
| bool | booleancheckbox | True / False |
Node.js β Creating custom properties
const newProperty =
await client.crm.properties.coreApi.create(
'contacts',
{
name:
'custom_score',
label:
'Custom score',
type:
'number',
fieldType:
'number',
groupName:
'contactinformation',
description:
'Score value synchronized from external system',
displayOrder:
-1,
hasUniqueValue:
false,
}
);
const enumProperty =
await client.crm.properties.coreApi.create(
'contacts',
{
name:
'customer_tier',
label:
'Customer Tier',
type:
'enumeration',
fieldType:
'select',
groupName:
'contactinformation',
options: [
{ label:
'platinum', value:
'platinum', displayOrder:
1 },
{ label:
'gold', value:
'gold', displayOrder:
2 },
{ label:
'Silver', value:
'silver', displayOrder:
3 },
],
}
);
2-7 Practical measures for rate limiting
Learn patterns to prevent 429 errors and safely process large amounts of data.
π Rate limiting specifications
Per 10 seconds: 100 requests
Per day: Depends on plan
BurstοΌ A short spike returns 429
Batch APIοΌ 1req = up to 100 items
β
Recommended measures
'{{contactName}} purchased {{productName}}' Avoid single operations
Exponential BackoffοΌ Retry at 429
Queue processing: Limit number of concurrent executions
Off-peak processing: Nightly batch execution
Node.js β Rate-limiting wrapper (recommended for production)
import pLimit
from 'p-limit';
const limit = pLimit(
5);
async function safeApiCall(fn, maxRetries =
4) {
for (
let attempt =
0; attempt < maxRetries; attempt++) {
try {
return await fn();
}
catch (err) {
const status = err.response?.status;
const isRetryable = status ===
429 || status ===
502 || status ===
503;
if (isRetryable && attempt < maxRetries -
1) {
const retryAfter = err.response?.headers[
'retry-after'];
const wait = retryAfter
? parseInt(retryAfter) *
1000
: Math.pow(
2, attempt) *
1000;
console.warn(
`Rate limited. Retrying in ${wait}ms (attempt ${attempt + 1})`);
await new Promise(r => setTimeout(r, wait));
}
else {
throw err;
}
}
}
}
const promises = contactIds.map(id =>
limit(() => safeApiCall(() =>
client.crm.contacts.basicApi.getById(id, [
'email',
'firstname'])
))
);
const contacts =
await Promise.all(promises);
2-8 Timeline API β Recording custom events
Record events from external systems (purchases, logins, operation history, etc.) on HubSpot's timeline.
π
Timeline API use cases
Actions that occur in external systems (purchases on e-commerce sites, logins to SaaS, in-app operations, etc.)
Help sales and support teams track every customer action by recording it in HubSpotβs contact timeline.
Now you can see it only in HubSpot.
Node.js β Creating Timeline events
const template =
await client.crm.timeline.templatesApi.create(appId, {
name:
'Purchase product',
headerTemplate:
'{{contactName}} purchased {{productName}}',
detailTemplate:
'Order ID: {{orderId}} / Amount: Β₯{{amount}}',
tokens: [
{ name:
'productName', type:
'string', label:
'Product name' },
{ name:
'orderId', type:
'string', label: 'Order ID' },
{ name:
'amount', type:
'number', label:
'Amount' },
],
objectType:
'CONTACT',
});
await client.crm.timeline.eventsApi.create({
eventTemplateId: template.id,
objectId: contactId,
tokens: {
productName:
'Pro plan',
orderId:
'ORD-2026-001',
amount:
49800,
},
timestamp:
new Date().toISOString(),
});
2-9 Summary of this chapter
Please review before proceeding to the next chapter (Marketing & CMS API).
β
Chapter 2 Checklist
- Understood the object structure of CRM API v3 (Contacts / Companies / Deals, etc.)
- CRUD operations (create, retrieve, update, delete) can be implemented using the Objects API.
- You can retrieve all records using pagination.
- You can implement searches that combine Search API's filters, sorts, and OR conditions.
- Understand the 10,000 search limit and workarounds for Search API
- Associations API allows you to create and delete associations between objects
- associations can be specified at the same time when creating an object
- You can implement batch operations of 100 items using Batch API.
- You can implement utilities that process large amounts of data in chunks.
- Understood rate limit support (exponential backoff + p-limit)
- You can create custom properties with the Properties API
- Log external events to HubSpot with Timeline API
About the next chapter (Chapter 3):
Learn APIs related to Marketing Hub and CMS. Implementation patterns for Forms API, Email API, Blog API, Pages API, and HubDB API.
I will explain it from the perspective of marketing automation.