hubspot-known-pitfalls

Identify and avoid HubSpot API anti-patterns and common integration mistakes. Use when reviewing HubSpot code, onboarding developers to HubSpot integrations, or auditing existing CRM integrations for best practice violations. Trigger with phrases like "hubspot mistakes", "hubspot anti-patterns", "hubspot pitfalls", "hubspot code review", "hubspot gotchas".

1,868 stars

Best use case

hubspot-known-pitfalls is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

Identify and avoid HubSpot API anti-patterns and common integration mistakes. Use when reviewing HubSpot code, onboarding developers to HubSpot integrations, or auditing existing CRM integrations for best practice violations. Trigger with phrases like "hubspot mistakes", "hubspot anti-patterns", "hubspot pitfalls", "hubspot code review", "hubspot gotchas".

Teams using hubspot-known-pitfalls should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/hubspot-known-pitfalls/SKILL.md --create-dirs "https://raw.githubusercontent.com/jeremylongshore/claude-code-plugins-plus-skills/main/plugins/saas-packs/hubspot-pack/skills/hubspot-known-pitfalls/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/hubspot-known-pitfalls/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How hubspot-known-pitfalls Compares

Feature / Agenthubspot-known-pitfallsStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

Identify and avoid HubSpot API anti-patterns and common integration mistakes. Use when reviewing HubSpot code, onboarding developers to HubSpot integrations, or auditing existing CRM integrations for best practice violations. Trigger with phrases like "hubspot mistakes", "hubspot anti-patterns", "hubspot pitfalls", "hubspot code review", "hubspot gotchas".

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

Related Guides

SKILL.md Source

# HubSpot Known Pitfalls

## Overview

Ten real-world HubSpot API anti-patterns with correct alternatives, covering authentication, rate limits, search, associations, and data handling.

## Prerequisites

- Access to HubSpot integration codebase
- Understanding of HubSpot CRM v3 API

## Instructions

### Pitfall 1: Using Deprecated API Keys

```typescript
// BAD: API keys were deprecated in 2022 and removed from SDK v10+
const client = new hubspot.Client({ apiKey: 'your-api-key' }); // REMOVED

// GOOD: Use private app access token
const client = new hubspot.Client({
  accessToken: process.env.HUBSPOT_ACCESS_TOKEN!, // pat-na1-xxxxx
  numberOfApiCallRetries: 3,
});
```

---

### Pitfall 2: Not Using Batch Operations

```typescript
// BAD: N API calls to read N contacts (hits rate limit fast)
for (const id of contactIds) {
  const contact = await client.crm.contacts.basicApi.getById(id, ['email']);
  // 100 contacts = 100 API calls
}

// GOOD: 1 API call for up to 100 contacts
const batch = await client.crm.contacts.batchApi.read({
  inputs: contactIds.map(id => ({ id })),
  properties: ['email', 'firstname'],
  propertiesWithHistory: [],
});
// 100 contacts = 1 API call
```

---

### Pitfall 3: Ignoring Search Limits

```typescript
// BAD: Search API has a hard limit of 10,000 results total
// You cannot page past this limit with `after`
const allResults = [];
let after = 0;
do {
  const page = await client.crm.contacts.searchApi.doSearch({
    filterGroups: [], properties: ['email'], limit: 100, after, sorts: [],
  });
  allResults.push(...page.results);
  after = page.paging?.next?.after;
} while (after); // STOPS at 10,000 regardless

// GOOD: Use getPage for full exports (no 10K limit)
async function* getAllContacts(properties: string[]) {
  let after: string | undefined;
  do {
    const page = await client.crm.contacts.basicApi.getPage(100, after, properties);
    yield* page.results;
    after = page.paging?.next?.after;
  } while (after); // No upper limit
}
```

---

### Pitfall 4: Wrong Association Type IDs

```typescript
// BAD: Guessing association type IDs
await client.crm.associations.v4.basicApi.create(
  'contacts', contactId, 'companies', companyId,
  [{ associationCategory: 'HUBSPOT_DEFINED', associationTypeId: 999 }] // wrong ID!
);
// Error: "association type id 999 doesn't exist between contact and company"

// GOOD: Use documented default type IDs
const ASSOC_TYPES = {
  CONTACT_TO_COMPANY: 1,    // Primary company
  CONTACT_TO_DEAL: 3,
  COMPANY_TO_DEAL: 5,
  CONTACT_TO_TICKET: 16,
  NOTE_TO_CONTACT: 202,
  TASK_TO_CONTACT: 204,
  NOTE_TO_DEAL: 214,
};

await client.crm.associations.v4.basicApi.create(
  'contacts', contactId, 'companies', companyId,
  [{ associationCategory: 'HUBSPOT_DEFINED', associationTypeId: ASSOC_TYPES.CONTACT_TO_COMPANY }]
);

// Or look up dynamically:
// GET /crm/v4/associations/contacts/companies/labels
```

---

### Pitfall 5: Creating Duplicate Contacts

```typescript
// BAD: Create without checking if contact exists
await client.crm.contacts.basicApi.create({
  properties: { email: 'jane@example.com', firstname: 'Jane' },
  associations: [],
});
// If jane@example.com exists: 409 Conflict error

// GOOD: Search first, then create or update
async function upsertContact(email: string, props: Record<string, string>) {
  const existing = await client.crm.contacts.searchApi.doSearch({
    filterGroups: [{
      filters: [{ propertyName: 'email', operator: 'EQ', value: email }],
    }],
    properties: ['email'], limit: 1, after: 0, sorts: [],
  });

  if (existing.results.length > 0) {
    return client.crm.contacts.basicApi.update(existing.results[0].id, { properties: props });
  }
  return client.crm.contacts.basicApi.create({
    properties: { email, ...props },
    associations: [],
  });
}

// BETTER: Use batch upsert (single API call)
await client.apiRequest({
  method: 'POST',
  path: '/crm/v3/objects/contacts/batch/upsert',
  body: {
    inputs: [{ properties: { email: 'jane@example.com', firstname: 'Jane' }, idProperty: 'email', id: 'jane@example.com' }],
  },
});
```

---

### Pitfall 6: Requesting All Properties

```typescript
// BAD: No properties specified = returns ALL default properties
const contact = await client.crm.contacts.basicApi.getById('123');
// Returns ~50+ properties you don't need, slower response

// GOOD: Request only what you need
const contact = await client.crm.contacts.basicApi.getById('123', [
  'email', 'firstname', 'lastname', 'lifecyclestage',
]);
// Returns only 4 properties, faster response
```

---

### Pitfall 7: Hardcoding Pipeline Stage IDs

```typescript
// BAD: Stage IDs are portal-specific, not universal
const deal = await client.crm.deals.basicApi.create({
  properties: {
    dealname: 'New Deal',
    dealstage: 'appointmentscheduled', // this default ID might not exist
    pipeline: 'default',               // might not be called "default"
  },
  associations: [],
});

// GOOD: Fetch pipelines first, then use IDs
const pipelines = await client.crm.pipelines.pipelinesApi.getAll('deals');
const salesPipeline = pipelines.results[0]; // or find by label
const firstStage = salesPipeline.stages.sort(
  (a, b) => Number(a.displayOrder) - Number(b.displayOrder)
)[0];

const deal = await client.crm.deals.basicApi.create({
  properties: {
    dealname: 'New Deal',
    dealstage: firstStage.id,
    pipeline: salesPipeline.id,
  },
  associations: [],
});
```

---

### Pitfall 8: Not Handling 409 Conflict on Associations

```typescript
// BAD: Creating association without checking if it exists
// (Some association types allow only one, e.g., primary company)
try {
  await client.crm.associations.v4.basicApi.create(
    'contacts', contactId, 'companies', companyId,
    [{ associationCategory: 'HUBSPOT_DEFINED', associationTypeId: 1 }]
  );
} catch (error) {
  // 409: Association already exists -- this is actually OK!
  // Don't treat as error
}

// GOOD: Catch 409 gracefully
async function ensureAssociation(
  fromType: string, fromId: string, toType: string, toId: string, typeId: number
) {
  try {
    await client.crm.associations.v4.basicApi.create(
      fromType, fromId, toType, toId,
      [{ associationCategory: 'HUBSPOT_DEFINED', associationTypeId: typeId }]
    );
  } catch (error: any) {
    if (error?.code !== 409) throw error;
    // Association already exists -- idempotent success
  }
}
```

---

### Pitfall 9: Polling Instead of Using Webhooks

```typescript
// BAD: Polling for changes wastes API calls
setInterval(async () => {
  const updated = await client.crm.contacts.searchApi.doSearch({
    filterGroups: [{
      filters: [{
        propertyName: 'lastmodifieddate',
        operator: 'GTE',
        value: String(Date.now() - 60000),
      }],
    }],
    properties: ['email'], limit: 100, after: 0, sorts: [],
  });
  processChanges(updated.results);
}, 60000); // 1,440 API calls/day just for polling

// GOOD: Use webhooks (0 API calls for change detection)
// Set up webhook subscription in your HubSpot public app for:
// - contact.propertyChange
// - deal.propertyChange
// - contact.creation
// See hubspot-webhooks-events skill
```

---

### Pitfall 10: Using the Wrong API Endpoint Version

```typescript
// BAD: Using legacy v1/v2 endpoints
const response = await fetch(
  `https://api.hubapi.com/contacts/v1/contact/email/${email}/profile`,
  { headers: { Authorization: `Bearer ${token}` } }
);
// Legacy endpoints may be deprecated and have different auth requirements

// GOOD: Use CRM v3 API with the SDK
const result = await client.crm.contacts.searchApi.doSearch({
  filterGroups: [{
    filters: [{ propertyName: 'email', operator: 'EQ', value: email }],
  }],
  properties: ['firstname', 'lastname', 'email'],
  limit: 1, after: 0, sorts: [],
});
```

## Quick Scan Commands

```bash
# Detect these pitfalls in your codebase
grep -rn "apiKey:" src/ --include="*.ts"                    # Pitfall 1
grep -rn "basicApi.getById" src/ | wc -l                   # Pitfall 2 (if > 10, use batch)
grep -rn "contacts/v1\|deals/v1\|companies/v2" src/        # Pitfall 10
grep -rn "setInterval.*hubspot\|setInterval.*crm" src/     # Pitfall 9
grep -rn "pat-na1-" src/ --include="*.ts" --include="*.js" # Token leak
```

## Quick Reference Card

| Pitfall | Detection | Fix |
|---------|-----------|-----|
| Deprecated API keys | `grep "apiKey:"` | Use `accessToken` |
| No batching | Many `getById` calls | Use `batchApi.read` |
| Search > 10K | Search with `after` past 10K | Use `getPage` |
| Wrong association IDs | 400 errors on associate | Use documented type IDs |
| Duplicate contacts | 409 on create | Search first or batch upsert |
| All properties | No `properties` param | Specify needed fields |
| Hardcoded stage IDs | Stage not found errors | Fetch pipelines dynamically |
| Association conflict | 409 on associate | Catch and ignore 409 |
| Polling for changes | High API call volume | Use webhooks |
| Legacy API versions | `/v1/` or `/v2/` URLs | Use CRM v3 SDK |

## Resources

- [CRM API Guide](https://developers.hubspot.com/docs/guides/api/crm/understanding-the-crm)
- [Batch Operations](https://developers.hubspot.com/docs/guides/api/crm/objects/contacts)
- [Search API Limits](https://developers.hubspot.com/docs/guides/api/crm/search)
- [Association Types](https://developers.hubspot.com/docs/guides/api/crm/associations)

Related Skills

windsurf-known-pitfalls

1868
from jeremylongshore/claude-code-plugins-plus-skills

Identify and avoid Windsurf anti-patterns and common mistakes. Use when onboarding new developers to Windsurf, reviewing AI workflow practices, or auditing Windsurf configuration for issues. Trigger with phrases like "windsurf mistakes", "windsurf anti-patterns", "windsurf pitfalls", "windsurf what not to do", "windsurf gotchas".

vercel-known-pitfalls

1868
from jeremylongshore/claude-code-plugins-plus-skills

Identify and avoid Vercel anti-patterns and common integration mistakes. Use when reviewing Vercel code for issues, onboarding new developers, or auditing existing Vercel deployments for best practice violations. Trigger with phrases like "vercel mistakes", "vercel anti-patterns", "vercel pitfalls", "vercel what not to do", "vercel code review".

supabase-known-pitfalls

1868
from jeremylongshore/claude-code-plugins-plus-skills

Avoid and fix the most common Supabase mistakes: exposing service_role key in client bundles, forgetting to enable RLS, not using connection pooling in serverless, .single() throwing on empty results, missing .select() after insert/update, not destructuring { data, error }, creating multiple client instances, and not using generated types. Use when reviewing Supabase code, onboarding developers, auditing an existing project, or debugging unexpected behavior. Trigger with phrases like "supabase mistakes", "supabase anti-patterns", "supabase pitfalls", "supabase code review", "supabase gotchas", "supabase debugging", "what not to do supabase", "supabase common errors".

snowflake-known-pitfalls

1868
from jeremylongshore/claude-code-plugins-plus-skills

Identify and avoid Snowflake anti-patterns and common mistakes in SQL, warehouse management, data loading, and access control. Use when reviewing Snowflake configurations, onboarding new users, or auditing existing Snowflake deployments for best practices. Trigger with phrases like "snowflake mistakes", "snowflake anti-patterns", "snowflake pitfalls", "snowflake what not to do", "snowflake code review".

shopify-known-pitfalls

1868
from jeremylongshore/claude-code-plugins-plus-skills

Identify and avoid Shopify API anti-patterns: ignoring userErrors, wrong API version, REST instead of GraphQL, missing GDPR webhooks, and webhook timeout issues. Trigger with phrases like "shopify mistakes", "shopify anti-patterns", "shopify pitfalls", "shopify what not to do", "shopify code review".

sentry-known-pitfalls

1868
from jeremylongshore/claude-code-plugins-plus-skills

Identify and fix common Sentry SDK pitfalls that cause silent data loss, cost overruns, and missed alerts. Covers 10 anti-patterns with fix code. Use when auditing Sentry config, debugging missing events, or reviewing SDK setup. Trigger: "sentry pitfalls", "sentry anti-patterns", "sentry mistakes", "why are sentry events missing".

salesforce-known-pitfalls

1868
from jeremylongshore/claude-code-plugins-plus-skills

Identify and avoid Salesforce anti-patterns including SOQL N+1, governor limit violations, and API waste. Use when reviewing Salesforce code for issues, onboarding new developers, or auditing existing Salesforce integrations for best practices violations. Trigger with phrases like "salesforce mistakes", "salesforce anti-patterns", "salesforce pitfalls", "salesforce what not to do", "salesforce code review".

retellai-known-pitfalls

1868
from jeremylongshore/claude-code-plugins-plus-skills

Retell AI known pitfalls — AI voice agent and phone call automation. Use when working with Retell AI for voice agents, phone calls, or telephony. Trigger with phrases like "retell known pitfalls", "retellai-known-pitfalls", "voice agent".

replit-known-pitfalls

1868
from jeremylongshore/claude-code-plugins-plus-skills

Avoid the top Replit anti-patterns: ephemeral filesystem, public secrets, port binding, Nix gotchas, and database limits. Use when reviewing Replit code, onboarding developers, or auditing existing Replit apps for common mistakes. Trigger with phrases like "replit mistakes", "replit anti-patterns", "replit pitfalls", "replit what not to do", "replit code review".

perplexity-known-pitfalls

1868
from jeremylongshore/claude-code-plugins-plus-skills

Identify and avoid Perplexity anti-patterns and common integration mistakes. Use when reviewing Perplexity code, onboarding new developers, or auditing existing integrations for best practices violations. Trigger with phrases like "perplexity mistakes", "perplexity anti-patterns", "perplexity pitfalls", "perplexity code review", "perplexity gotchas".

openrouter-known-pitfalls

1868
from jeremylongshore/claude-code-plugins-plus-skills

Avoid common OpenRouter integration mistakes and gotchas. Use proactively when starting a new integration or reviewing existing code. Triggers: 'openrouter pitfalls', 'openrouter gotchas', 'openrouter mistakes', 'openrouter best practices'.

notion-known-pitfalls

1868
from jeremylongshore/claude-code-plugins-plus-skills

Common Notion API mistakes: wrong page ID format (dashes), rich text array structure, block children not returned with page, pagination required for all lists, 3 req/sec shared across endpoints, not sharing pages with integration. Use when debugging or reviewing Notion code. Trigger with phrases like "notion mistakes", "notion pitfalls", "notion common errors", "notion gotchas", "notion debugging".