xiaohongshu
xiaohongshu
hubspot-nango-integration
Use when writing HubSpot integration code in Nango - HubSpot-specific guidance on Search API for incremental syncs, property name variations, rate limits, and OAuth introspection
Full skill instructions
Use this skill when writing, reviewing, or troubleshooting HubSpot-specific aspects of Nango integrations.
Focus: This skill covers HubSpot API quirks, not general Nango patterns.
IMPORTANT: HubSpot incremental syncs can ONLY be achieved via the Search API endpoint.
Why: Only the Search API supports filtering by hs_lastmodifieddate (or lastmodifieddate for contacts), which is essential for incremental syncs.
Trade-off: The Search API has a lower rate limit than other endpoints:
Implication: When designing HubSpot integrations, you must balance:
// POST /crm/v3/objects/{object_type}/search
// Examples:
// POST /crm/v3/objects/contacts/search
// POST /crm/v3/objects/companies/search
// POST /crm/v3/objects/deals/search
Property Name Varies by Object:
lastmodifieddate (not hs_lastmodifieddate)hs_lastmodifieddateDate Format: UNIX timestamp in milliseconds
Example Request Body:
{
"filterGroups": [{
"filters": [{
"propertyName": "hs_lastmodifieddate",
"operator": "GTE",
"value": "1579514400000"
}]
}],
"properties": ["id", "name", "hs_lastmodifieddate"],
"limit": 100,
"after": "cursor_token_here"
}
Supported Operators:
GTE (greater than or equal)GT (greater than)LTE (less than or equal)LT (less than)BETWEEN (requires both value and highValue)HubSpot provides OAuth token introspection endpoints to validate tokens and retrieve associated user/account information:
Endpoints:
/oauth/v1/access-tokens/:token/oauth/v1/refresh-tokens/:tokenUse Cases:
Example Usage in Nango:
// Validate and inspect refresh token
const response = await nango.get({
endpoint: `/oauth/v1/refresh-tokens/${refreshToken}`,
baseUrlOverride: 'https://api.hubapi.com'
});
const { user_id, hub_id, scopes } = response.data;
HubSpot provides powerful schema introspection endpoints that allow you to discover:
This is critical for building flexible integrations that adapt to customer-specific customizations.
1. List All Schemas (Custom + Standard):
GET /crm-object-schemas/v3/schemas
Returns all custom object schemas. Each schema includes properties, associations, and metadata.
2. Get Specific Object Schema:
GET /crm-object-schemas/v3/schemas/{objectTypeId}
Returns detailed schema for a specific object (works for both custom and standard objects).
Required Scope: crm.schemas.custom.read
interface HubSpotSchemaResult {
id: string // Object type ID (e.g., "0-1" for Contact)
name: string // Object name
objectTypeId: string // Same as id
primaryDisplayProperty: string // Which property to use as display name
properties: HubSpotProperty[] // All properties including custom fields
associations: HubSpotAssociation[] // All associations
}
interface HubSpotProperty {
name: string // Property ID (e.g., "firstname", "custom_field_1")
label: string // Display label
type: string // HubSpot type (string, number, date, enumeration, etc.)
fieldType: string // Field type (text, textarea, select, etc.)
options?: Array<{ // For enumeration/select fields
label: string
value: string
}>
}
interface HubSpotAssociation {
id: string // Association ID
name: string // Association name
fromObjectTypeId: string // Source object
toObjectTypeId: string // Target object
}
Advanced HubSpot integrations use a two-phase sync pattern:
Phase 1: Schema Sync - Discover structure
Phase 2: Records Sync - Fetch data
Without Introspection:
// Hardcoded - breaks if customer adds custom fields
const properties = ['firstname', 'lastname', 'email'];
With Introspection:
// Dynamic - adapts to customer's schema
const schema = await getSchema(nango, objectId);
const properties = schema.properties.map(p => p.name);
// Includes all custom fields automatically!
Standard Objects have numeric IDs:
0-1 - Contact0-2 - Company0-3 - Deal0-5 - Ticket0-7 - Product0-8 - Line Item0-136 - Leadowner - Owner (special case)Custom Objects have different ID formats (provided by HubSpot when created).
Key Insight: HubSpot custom fields are just additional properties in the schema. They appear alongside standard fields.
Standard Contact Fields:
firstname, lastname, email (built-in)Custom Contact Fields (examples):
custom_field_name (user-defined)department, employee_id, etc.All appear in the same properties array from schema introspection.
// Fetch schema for contacts
const schema = await nango.get({
endpoint: '/crm-object-schemas/v3/schemas/0-1', // Contact
retries: 10
});
// Properties include both standard AND custom fields
const allProperties = schema.data.properties.map(p => p.name);
// ['firstname', 'lastname', 'email', 'custom_field_1', 'department', ...]
HubSpot associations link objects together (e.g., Contact → Company, Deal → Contact).
Association Types:
HUBSPOT_DEFINED - Built-in associations (Contact to Company)USER_DEFINED - Custom associations (Custom Object to Contact)Key Pattern: Associations are discovered via schema introspection, then included in record fetch.
// 1. Get associations from schema
const schema = await getSchema(nango, objectId);
const associations = schema.associations
.filter(a => a.fromObjectTypeId === objectId)
.map(a => a.toObjectTypeId);
// 2. Fetch records with associations
const response = await nango.get({
endpoint: `/crm/v3/objects/${objectType}`,
params: {
properties: properties.join(','),
associations: associations.join(',') // Include in request!
}
});
// 3. Response includes associations in each record
const record = response.data.results[0];
// record.associations.companies.results = [{ id: "123" }, ...]
HubSpot has limits on URL length and number of properties per request. For objects with many custom fields:
Problem: 100+ properties can exceed URL limits
Solution: Chunk properties into groups, fetch multiple times, merge results
// Split properties into chunks of 50
const chunks = chunkArray(properties, 50);
const recordMap = new Map();
for (const propertyChunk of chunks) {
const response = await nango.get({
endpoint: `/crm/v3/objects/contacts`,
params: {
properties: propertyChunk.join(','),
after: cursor
}
});
// Merge properties from multiple requests
for (const record of response.data.results) {
const existing = recordMap.get(record.id);
if (existing) {
recordMap.set(record.id, {
...existing,
properties: { ...existing.properties, ...record.properties }
});
} else {
recordMap.set(record.id, record);
}
}
}
// All properties now merged in recordMap
HubSpot has special "owner" fields that reference the Owner object:
hubspot_owner_idhs_owner_idThese should be treated as lookups/foreign keys to the Owner object (owner).
// When mapping fields, detect owner fields
if (['hubspot_owner_id', 'hs_owner_id'].includes(property.name)) {
// This is a reference to Owner object, not a simple field
field.type = 'lookup';
field.externalLinkTargetTable = 'owner';
}
HubSpot Endpoint: /crm/v3/objects/{object} (GET with query params)
Rate Limit: 190 calls per 10 seconds (up to 250 with capacity pack)
// HubSpot-specific considerations:
const properties = [
'firstname', // HubSpot uses lowercase, no camelCase
'lastname',
'email',
'jobtitle',
'createdate', // Note: 'createdate' not 'createdDate'
'hubspot_owner_id' // HubSpot prefix for system properties
];
const config: ProxyConfiguration = {
endpoint: '/crm/v3/objects/contacts', // Standard CRM endpoint
params: {
properties: properties.join(',') // HubSpot requires comma-separated string
},
// ... pagination config
};
HubSpot Endpoint: /crm/v3/objects/{object}/search (POST with filter body)
Rate Limit: 4 requests per second (much lower!)
Why Required: Only endpoint supporting hs_lastmodifieddate filtering
// HubSpot-specific: Convert lastSyncDate to UNIX timestamp in milliseconds
const lastSyncDate = nango.lastSyncDate?.toISOString().slice(0, -8).replace('T', ' ');
const queryDate = lastSyncDate ? Date.parse(lastSyncDate) : Date.now() - 86400000;
const payload = {
endpoint: '/crm/v3/objects/tickets/search', // POST, not GET
data: {
sorts: [{
propertyName: 'hs_lastmodifieddate', // HubSpot-specific property
direction: 'DESCENDING'
}],
properties: TICKET_PROPERTIES, // Array, not comma-separated string
filterGroups: [{
filters: [{
propertyName: 'hs_lastmodifieddate', // Key for incremental
operator: 'GT', // Greater than last sync
value: queryDate // UNIX ms timestamp
}]
}],
limit: `${MAX_PAGE}`,
after: afterLink // Cursor for pagination
},
retries: 10
};
const response = await nango.post(payload);
HubSpot Pagination: Response includes paging.next.after cursor token.
HubSpot Endpoint: /crm/v3/objects/{object} (POST for create)
Rate Limit: 190 calls per 10 seconds (higher than Search API)
// HubSpot requires properties wrapped in 'properties' object
const hubSpotContact = {
properties: {
firstname: input.firstName,
lastname: input.lastName,
email: input.email,
jobtitle: input.jobTitle
}
};
const config: ProxyConfiguration = {
endpoint: 'crm/v3/objects/contacts', // Standard endpoint, NOT /search
data: hubSpotContact,
retries: 3
};
const response = await nango.post(config);
// HubSpot returns: { id, properties: {...}, createdAt, updatedAt, archived }
Different HubSpot objects may have different property names:
contacts, companies, deals, tickets, products, line_itemsSearch API Pagination:
after parameterpaging.next.after for the next pageIf you hit the 10,000 limit:
Standard Endpoints: 190 calls per 10 seconds (250 with capacity pack) Search API: 4 requests per second per token
Implication: Search API can make ~24 requests per 10 seconds vs 190 for standard endpoints.
When implementing HubSpot integrations, verify these HubSpot-specific requirements:
/crm/v3/objects/{object}/search) for incremental syncs ONLY/crm/v3/objects/{object}) for full syncs and actionslastmodifieddate (contacts) or hs_lastmodifieddate (other objects)firstname, not firstName)hubspot_owner_id (with prefix) for owner referencesDate.parse())properties object for create/updateproperties object, not flat structurepaging.next.after cursor (not offset-based)hubspot_owner_id, hs_owner_id)crm.schemas.custom.read scope for schema accesshs_lastmodifieddate filtering; must use Search APIhs_lastmodifieddate for contacts (should be lastmodifieddate)firstname, not firstName)properties wrapper - Create/update requests need { properties: {...} }hubspot_owner_id should be treated as lookup to Owner objectUse this skill for HubSpot-specific questions:
lastmodifieddate vs hs_lastmodifieddate)Do NOT use for generic Nango patterns - focus on HubSpot API specifics only.
https://developers.hubspot.com/docs/api/crm/propertieshttps://developers.hubspot.com/docs/guides/api/crm/using-object-apishttps://developers.hubspot.com/docs/api/crm/searchhttps://developers.hubspot.com/docs/api/working-with-oauthhttps://developers.hubspot.com/docs/api/usage-detailshttps://developers.hubspot.com/docs/api/crm/understanding-the-crmhttps://developers.hubspot.com/docs/apps/legacy-apps/authentication/scopesxiaohongshu
technical spec
product ux expert
database patterns
Conduct multi-agent task orchestration and workflow coordination.
Initialize project with Conductor artifacts (product definition,
Expert in web animations, transitions, and motion design using Framer Motion and CSS
Creates Mermaid and ASCII diagrams for flowcharts, architecture, ERDs, state machines, mindmaps, and more. Use when user mentions diagram, flowchart, mermaid, ASCII diagram, text diagram, terminal diagram, visualize, C4, mindmap, architecture diagram, sequence diagram, ERD, or needs visual docume...
PostgreSQL bindings for H3 hexagonal grid system. Use when working with H3 cells in Postgres, including spatial indexing, geometry/geography integration, and raster analysis.
Context-Driven Development skill for projects using Conductor. Use this skill when you detect a `conductor/` directory in the project, when working on tasks defined in a `plan.md` file, or when the user asks about tracks, specs, or plans. Automatically applies TDD workflow, tracks task completion...
Display project status, active tracks, and next actions
Official Stakpak application containerization standard operating procedure, a step-by-step guidline to properly dockerize applications. This is a rule book curated by the Stakpak Team.
Generate, edit, and beat-sync AI video with leading models in one workspace.
The world's fastest calendar for remote work
Transform Your Design with AI Designer by ImgCreator.ai
Revolutionizing Video Production with AI-Powered Creativity
Extend an image past the frame and let AI fill the new aspect ratio.
Discover your celebrity doppelgänger with StarByFace!
ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.
Opus.ai: Revolutionize Your Web Experience