From hubspot-pack
Diagnose and fix HubSpot API errors like 401, 403, 409 with real JSON responses, causes, and solutions including curl token checks and scope regeneration.
npx claudepluginhub jeremylongshore/claude-code-plugins-plus-skills --plugin hubspot-packThis skill is limited to using the following tools:
Quick reference for the most common HubSpot API errors, their real error response bodies, and solutions.
Identifies HubSpot API anti-patterns like deprecated auth, missing batch ops, search limits, and wrong associations, with TypeScript fixes for code reviews and audits.
Provides expert patterns for HubSpot CRM integration including OAuth authentication, CRM objects, associations, batch operations, webhooks, and custom objects using Node.js and Python SDKs.
Diagnoses and fixes Intercom API errors by HTTP status code and type, with error shapes, causes, curl verification, and TypeScript handling examples. Use for failed requests or integrations.
Share bugs, ideas, or general feedback.
Quick reference for the most common HubSpot API errors, their real error response bodies, and solutions.
@hubspot/api-client installedCheck the HTTP status code and response body. HubSpot returns structured errors:
{
"status": "error",
"message": "One or more validation errors occurred",
"correlationId": "abc123-def456",
"category": "VALIDATION_ERROR",
"errors": [
{
"message": "Property values were not valid: [{\"isValid\":false,\"message\":\"...\"}]",
"context": { "propertyName": "email" }
}
]
}
Real response:
{
"status": "error",
"message": "Authentication credentials not found. This API supports OAuth 2.0 authentication and you can find more details at https://developers.hubspot.com/docs/methods/auth/oauth-overview",
"correlationId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"category": "INVALID_AUTHENTICATION"
}
Causes:
Authorization: Bearer headerFix:
# Verify token is set and valid
curl -s https://api.hubapi.com/crm/v3/objects/contacts?limit=1 \
-H "Authorization: Bearer $HUBSPOT_ACCESS_TOKEN" | jq .status
# Should return null (success) or "error"
Real response:
{
"status": "error",
"message": "This access token does not have proper permissions. (requires any of [crm.objects.contacts.write])",
"correlationId": "...",
"category": "MISSING_SCOPES"
}
Fix: Add the missing scope to your private app in Settings > Integrations > Private Apps, then regenerate the token.
Real response:
{
"status": "error",
"message": "Contact already exists. Existing ID: 12345",
"correlationId": "...",
"category": "CONFLICT"
}
Fix: Search before creating, or use batch upsert:
// Use search-first pattern
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) {
await client.crm.contacts.basicApi.update(existing.results[0].id, { properties });
} else {
await client.crm.contacts.basicApi.create({ properties, associations: [] });
}
Real response:
{
"status": "error",
"message": "You have reached your secondly limit.",
"correlationId": "...",
"category": "RATE_LIMITS"
}
Headers returned:
X-HubSpot-RateLimit-Daily: 500000
X-HubSpot-RateLimit-Daily-Remaining: 499950
X-HubSpot-RateLimit-Secondly: 10
X-HubSpot-RateLimit-Secondly-Remaining: 0
Retry-After: 1
Fix: Honor Retry-After header and use SDK built-in retries:
const client = new hubspot.Client({
accessToken: process.env.HUBSPOT_ACCESS_TOKEN!,
numberOfApiCallRetries: 3, // auto-retries 429s with backoff
});
Real response:
{
"status": "error",
"message": "Property values were not valid: [{\"isValid\":false,\"message\":\"Property \\\"nonexistent_field\\\" does not exist\",\"error\":\"PROPERTY_DOESNT_EXIST\",\"name\":\"nonexistent_field\"}]",
"correlationId": "...",
"category": "VALIDATION_ERROR"
}
Fix:
// List available properties for an object type
// GET /crm/v3/properties/{objectType}
const properties = await client.crm.properties.coreApi.getAll('contacts');
const propNames = properties.results.map(p => p.name);
console.log('Available contact properties:', propNames);
Real response:
{
"status": "error",
"message": "association type id 999 doesn't exist between contact and company",
"correlationId": "...",
"category": "VALIDATION_ERROR"
}
Common association type IDs:
| From | To | TypeId | Label |
|---|---|---|---|
| Contact | Company | 1 | Primary |
| Contact | Deal | 3 | -- |
| Company | Deal | 5 | -- |
| Contact | Ticket | 16 | -- |
| Note | Contact | 202 | -- |
| Task | Contact | 204 | -- |
| Note | Deal | 214 | -- |
Real response:
{
"status": "error",
"message": "Object not found. objectType=contacts, objectId=12345. It may have been deleted.",
"correlationId": "...",
"category": "OBJECT_NOT_FOUND"
}
Fix: The record may be archived. Check with archived=true:
const contact = await client.crm.contacts.basicApi.getById(
'12345',
['email'],
undefined,
undefined,
true // archived = true
);
import { HttpError } from '@hubspot/api-client/lib/codegen/crm/contacts';
async function handleHubSpotError(error: any): Promise<void> {
const status = error?.code || error?.statusCode || 500;
const body = typeof error?.body === 'string' ? JSON.parse(error.body) : error?.body || {};
console.error(`HubSpot API Error [${status}]`, {
message: body.message,
category: body.category,
correlationId: body.correlationId,
errors: body.errors,
});
// Save correlationId for support tickets
if (status >= 500) {
console.error('HubSpot server error. Check https://status.hubspot.com');
}
}
# Check HubSpot API status
curl -s https://status.hubspot.com/api/v2/summary.json | jq '.status.description'
# Verify token and scopes
curl -s https://api.hubapi.com/crm/v3/objects/contacts?limit=1 \
-H "Authorization: Bearer $HUBSPOT_ACCESS_TOKEN" \
-w "\nHTTP Status: %{http_code}\n"
# Check rate limit headers
curl -sI https://api.hubapi.com/crm/v3/objects/contacts?limit=1 \
-H "Authorization: Bearer $HUBSPOT_ACCESS_TOKEN" \
| grep -i ratelimit
For comprehensive debugging, see hubspot-debug-bundle.