🟒 HubSpot Developer Practical Textbook β€” 2026 Edition Developer Edition
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.

CRM API v3
Node.js SDK usage example
Time required: Approximately 120 minutes
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.

objectAPI pathPurpose
Contacts/crm/v3/objects/contactsPersonal information of prospective customers/customers
Companies/crm/v3/objects/companiesBusiness partner information
Deals/crm/v3/objects/dealsBusiness negotiations/proposals
Tickets/crm/v3/objects/ticketssupport ticket
Products/crm/v3/objects/productsProducts/Product Catalog
Line Items/crm/v3/objects/line_itemsLines associated with opportunities
Quotes/crm/v3/objects/quotesEstimate
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 }); // ── Create ────────────────────────────── 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); // ── Acquisition (ID specification) ──────────────────── const contact = await client.crm.contacts.basicApi.getById( newContact.id, ['email', 'firstname', 'lastname', 'hs_lead_status'] ); // ── Update ────────────────────────────── await client.crm.contacts.basicApi.update(newContact.id, { properties: { hs_lead_status: 'IN_PROGRESS', lifecyclestage: 'opportunity', }, }); // ── Delete (Archive) ────────────────── await client.crm.contacts.basicApi.archive(newContact.id);
Node.js β€” List acquisition and pagination
// Get all contacts with pagination async function getAllContacts() { const allContacts = []; let after = undefined; do { const page = await client.crm.contacts.basicApi.getPage( 100, // limit (maximum 100) after, // pagination token ['email', 'firstname', 'lastname', 'createdate'] ); allContacts.push(...page.results); after = page.paging?.next?.after; // next page token // Rate limit countermeasure: 100ms wait 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.

operatormeaningexample
EQequallifecyclestage = "customer"
NEQnot equalhs_lead_status β‰  "UNQUALIFIED"
GT / GTEgreater than/more thancreatedate > specific date and time
LT / LTEless than/less thanamount < 100000
CONTAINS_TOKENContains token (partial match)email contains "@example.com"
INany in the listcountry IN ["JP", "US", "UK"]
NOT_INnone in listlifecyclestage NOT IN ["other"]
HAS_PROPERTYproperty existsphone is set
NOT_HAS_PROPERTYproperty does not existemail not set
BETWEENwithin rangeamount BETWEEN 10000 AND 50000
Node.js β€” Search API implementation example
// Example: Find contacts with customer stage "customer" created within the last 30 days 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)
// Example: Contacts with lifecycle "lead" OR "opportunity" // The array of filterGroups is an OR condition, the filters in groups is an AND condition 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)); // throttle } return results; } // Usage example 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 associationsfromObjecttoObjecttypeId
Contact β†’ Company (main affiliation)contactscompanies1
Company β†’ Contactcompaniescontacts2
Contact β†’ Business negotiationcontactsdeals4
Business β†’ Contactdealscontacts3
Company β†’ Business negotiationcompaniesdeals6
Business negotiation β†’ Companydealscompanies5
Contact β†’ Ticketcontactstickets16
Node.js β€” Associations CRUD
// Associate the contact with the company await client.crm.associations.v4.basicApi.create( 'contacts', // fromObjectType contactId, // fromObjectId 'companies', // toObjectType companyId, // toObjectId [{ associationCategory: 'HUBSPOT_DEFINED', associationTypeId: 1 }] ); // Get the company associated with the contact const associations = await client.crm.associations.v4.basicApi.getPage( 'contacts', contactId, 'companies' ); console.log(associations.results.map(a => a.toObjectId)); // delete the association await client.crm.associations.v4.basicApi.archive( 'contacts', contactId, 'companies', companyId );
Node.js β€” Object creation and association at the same time
// Associate contacts and companies all at once while creating an opportunity 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
// Create up to 100 contacts at once 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' } }, // ... up to 100 items ], }); console.log(`Created ${batchCreateResult.results.length} contacts`);
Node.js β€” Batch updates
// Update multiple contacts at once 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
/** * Divide a large number of records into chunks of 100 records and process them using Batch API * @param {Array} items - the array to process * @param {Function} batchFn - Function that calls the Batch API * @param {number} chunkSize - chunk size (up to 100) */ 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); // Rate limiting measure: wait 200ms between chunks if (i + chunkSize < items.length) { await new Promise(r => setTimeout(r, 200)); } } return results; } // Usage example: Update 500 contacts at once 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 typefieldTypePurpose
stringtext / textareatext input
numbernumberNumerical value (amount, score, etc.)
enumerationselect / radio / checkboxMultiple choice. requires options list
datedateDate (YYYY-MM-DD)
datetimedateDate and time (Unix milliseconds)
boolbooleancheckboxTrue / False
Node.js β€” Creating custom properties
// Add custom properties to the contact 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, } ); // Create enumeration property (with choices) 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'; // npm install p-limit /** * API wrapper that combines rate limiting, retries, and concurrency control */ const limit = pLimit(5); // Limit concurrency to maximum 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; } } } } // Use case: Process 100 contacts in parallel & rate limited 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
// Step 1: Create event template (first time only) 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', }); // Step 2: Record the event 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.