opencode-skills-collection 1.0.185 → 1.0.187

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/bundled-skills/.antigravity-install-manifest.json +5 -1
  2. package/bundled-skills/3d-web-experience/SKILL.md +152 -37
  3. package/bundled-skills/agent-evaluation/SKILL.md +1088 -26
  4. package/bundled-skills/agent-memory-systems/SKILL.md +1037 -25
  5. package/bundled-skills/agent-tool-builder/SKILL.md +668 -16
  6. package/bundled-skills/ai-agents-architect/SKILL.md +271 -31
  7. package/bundled-skills/ai-product/SKILL.md +716 -26
  8. package/bundled-skills/ai-wrapper-product/SKILL.md +450 -44
  9. package/bundled-skills/algolia-search/SKILL.md +867 -15
  10. package/bundled-skills/autonomous-agents/SKILL.md +1033 -26
  11. package/bundled-skills/aws-serverless/SKILL.md +1046 -35
  12. package/bundled-skills/azure-functions/SKILL.md +1318 -19
  13. package/bundled-skills/browser-automation/SKILL.md +1065 -28
  14. package/bundled-skills/browser-extension-builder/SKILL.md +159 -32
  15. package/bundled-skills/bullmq-specialist/SKILL.md +347 -16
  16. package/bundled-skills/clerk-auth/SKILL.md +796 -15
  17. package/bundled-skills/computer-use-agents/SKILL.md +1870 -28
  18. package/bundled-skills/context-window-management/SKILL.md +271 -18
  19. package/bundled-skills/conversation-memory/SKILL.md +453 -24
  20. package/bundled-skills/crewai/SKILL.md +252 -46
  21. package/bundled-skills/discord-bot-architect/SKILL.md +1207 -34
  22. package/bundled-skills/docs/integrations/jetski-cortex.md +3 -3
  23. package/bundled-skills/docs/integrations/jetski-gemini-loader/README.md +1 -1
  24. package/bundled-skills/docs/maintainers/repo-growth-seo.md +3 -3
  25. package/bundled-skills/docs/maintainers/skills-update-guide.md +1 -1
  26. package/bundled-skills/docs/users/bundles.md +1 -1
  27. package/bundled-skills/docs/users/claude-code-skills.md +1 -1
  28. package/bundled-skills/docs/users/gemini-cli-skills.md +1 -1
  29. package/bundled-skills/docs/users/getting-started.md +1 -1
  30. package/bundled-skills/docs/users/kiro-integration.md +1 -1
  31. package/bundled-skills/docs/users/usage.md +4 -4
  32. package/bundled-skills/docs/users/visual-guide.md +4 -4
  33. package/bundled-skills/email-systems/SKILL.md +646 -26
  34. package/bundled-skills/faf-expert/SKILL.md +221 -0
  35. package/bundled-skills/faf-wizard/SKILL.md +252 -0
  36. package/bundled-skills/file-uploads/SKILL.md +212 -11
  37. package/bundled-skills/firebase/SKILL.md +646 -16
  38. package/bundled-skills/gcp-cloud-run/SKILL.md +1117 -32
  39. package/bundled-skills/graphql/SKILL.md +1026 -27
  40. package/bundled-skills/hubspot-integration/SKILL.md +804 -19
  41. package/bundled-skills/idea-darwin/SKILL.md +120 -0
  42. package/bundled-skills/inngest/SKILL.md +431 -16
  43. package/bundled-skills/interactive-portfolio/SKILL.md +342 -44
  44. package/bundled-skills/langfuse/SKILL.md +296 -41
  45. package/bundled-skills/langgraph/SKILL.md +259 -50
  46. package/bundled-skills/micro-saas-launcher/SKILL.md +343 -44
  47. package/bundled-skills/neon-postgres/SKILL.md +572 -15
  48. package/bundled-skills/nextjs-supabase-auth/SKILL.md +269 -21
  49. package/bundled-skills/notion-template-business/SKILL.md +371 -44
  50. package/bundled-skills/personal-tool-builder/SKILL.md +537 -44
  51. package/bundled-skills/plaid-fintech/SKILL.md +825 -19
  52. package/bundled-skills/prompt-caching/SKILL.md +438 -25
  53. package/bundled-skills/rag-engineer/SKILL.md +271 -29
  54. package/bundled-skills/salesforce-development/SKILL.md +912 -19
  55. package/bundled-skills/satori/SKILL.md +54 -0
  56. package/bundled-skills/scroll-experience/SKILL.md +381 -44
  57. package/bundled-skills/segment-cdp/SKILL.md +817 -19
  58. package/bundled-skills/shopify-apps/SKILL.md +1475 -19
  59. package/bundled-skills/slack-bot-builder/SKILL.md +1162 -28
  60. package/bundled-skills/telegram-bot-builder/SKILL.md +152 -37
  61. package/bundled-skills/telegram-mini-app/SKILL.md +445 -44
  62. package/bundled-skills/trigger-dev/SKILL.md +916 -27
  63. package/bundled-skills/twilio-communications/SKILL.md +1310 -28
  64. package/bundled-skills/upstash-qstash/SKILL.md +898 -27
  65. package/bundled-skills/vercel-deployment/SKILL.md +637 -39
  66. package/bundled-skills/viral-generator-builder/SKILL.md +132 -37
  67. package/bundled-skills/voice-agents/SKILL.md +937 -27
  68. package/bundled-skills/voice-ai-development/SKILL.md +375 -46
  69. package/bundled-skills/workflow-automation/SKILL.md +982 -29
  70. package/bundled-skills/zapier-make-patterns/SKILL.md +772 -27
  71. package/package.json +1 -1
@@ -1,47 +1,832 @@
1
1
  ---
2
2
  name: hubspot-integration
3
- description: "Authentication for single-account integrations"
3
+ description: Expert patterns for HubSpot CRM integration including OAuth
4
+ authentication, CRM objects, associations, batch operations, webhooks, and
5
+ custom objects. Covers Node.js and Python SDKs.
4
6
  risk: unknown
5
- source: "vibeship-spawner-skills (Apache 2.0)"
6
- date_added: "2026-02-27"
7
+ source: vibeship-spawner-skills (Apache 2.0)
8
+ date_added: 2026-02-27
7
9
  ---
8
10
 
9
11
  # HubSpot Integration
10
12
 
13
+ Expert patterns for HubSpot CRM integration including OAuth authentication,
14
+ CRM objects, associations, batch operations, webhooks, and custom objects.
15
+ Covers Node.js and Python SDKs.
16
+
11
17
  ## Patterns
12
18
 
13
19
  ### OAuth 2.0 Authentication
14
20
 
15
21
  Secure authentication for public apps
16
22
 
23
+ **When to use**: Building public app or multi-account integration
24
+
25
+ ### Template
26
+
27
+ // OAuth 2.0 flow for HubSpot
28
+ import { Client } from "@hubspot/api-client";
29
+
30
+ // Environment variables
31
+ const CLIENT_ID = process.env.HUBSPOT_CLIENT_ID;
32
+ const CLIENT_SECRET = process.env.HUBSPOT_CLIENT_SECRET;
33
+ const REDIRECT_URI = process.env.HUBSPOT_REDIRECT_URI;
34
+ const SCOPES = "crm.objects.contacts.read crm.objects.contacts.write";
35
+
36
+ // Step 1: Generate authorization URL
37
+ function getAuthUrl(): string {
38
+ const authUrl = new URL("https://app.hubspot.com/oauth/authorize");
39
+ authUrl.searchParams.set("client_id", CLIENT_ID);
40
+ authUrl.searchParams.set("redirect_uri", REDIRECT_URI);
41
+ authUrl.searchParams.set("scope", SCOPES);
42
+ return authUrl.toString();
43
+ }
44
+
45
+ // Step 2: Handle OAuth callback
46
+ async function handleOAuthCallback(code: string) {
47
+ const response = await fetch("https://api.hubapi.com/oauth/v1/token", {
48
+ method: "POST",
49
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
50
+ body: new URLSearchParams({
51
+ grant_type: "authorization_code",
52
+ client_id: CLIENT_ID,
53
+ client_secret: CLIENT_SECRET,
54
+ redirect_uri: REDIRECT_URI,
55
+ code: code,
56
+ }),
57
+ });
58
+
59
+ const tokens = await response.json();
60
+ // {
61
+ // access_token: "xxx",
62
+ // refresh_token: "xxx",
63
+ // expires_in: 1800 // 30 minutes
64
+ // }
65
+
66
+ // Store tokens securely
67
+ await storeTokens(tokens);
68
+
69
+ return tokens;
70
+ }
71
+
72
+ // Step 3: Refresh access token (before expiry)
73
+ async function refreshAccessToken(refreshToken: string) {
74
+ const response = await fetch("https://api.hubapi.com/oauth/v1/token", {
75
+ method: "POST",
76
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
77
+ body: new URLSearchParams({
78
+ grant_type: "refresh_token",
79
+ client_id: CLIENT_ID,
80
+ client_secret: CLIENT_SECRET,
81
+ refresh_token: refreshToken,
82
+ }),
83
+ });
84
+
85
+ return response.json();
86
+ }
87
+
88
+ // Step 4: Create authenticated client
89
+ function createClient(accessToken: string): Client {
90
+ const hubspotClient = new Client({ accessToken });
91
+ return hubspotClient;
92
+ }
93
+
94
+ ### Notes
95
+
96
+ - Access tokens expire in 30 minutes
97
+ - Refresh tokens before expiry
98
+ - Store refresh tokens securely
99
+ - Rotate tokens every 6 months
100
+
17
101
  ### Private App Token
18
102
 
19
103
  Authentication for single-account integrations
20
104
 
105
+ **When to use**: Building internal integration for one HubSpot account
106
+
107
+ ### Template
108
+
109
+ // Private App Token - simpler for single account
110
+ import { Client } from "@hubspot/api-client";
111
+
112
+ // Create client with private app token
113
+ const hubspotClient = new Client({
114
+ accessToken: process.env.HUBSPOT_PRIVATE_APP_TOKEN,
115
+ });
116
+
117
+ // Private app tokens don't expire
118
+ // But should be rotated every 6 months for security
119
+
120
+ // Example: Get contacts
121
+ async function getContacts() {
122
+ try {
123
+ const response = await hubspotClient.crm.contacts.basicApi.getPage(
124
+ 100, // limit
125
+ undefined, // after cursor
126
+ ["firstname", "lastname", "email", "phone"], // properties
127
+ );
128
+
129
+ return response.results;
130
+ } catch (error) {
131
+ if (error.code === 429) {
132
+ // Rate limited - implement backoff
133
+ const retryAfter = error.headers?.["retry-after"] || 10;
134
+ await sleep(retryAfter * 1000);
135
+ return getContacts();
136
+ }
137
+ throw error;
138
+ }
139
+ }
140
+
141
+ // Python equivalent
142
+ // from hubspot import HubSpot
143
+ //
144
+ // client = HubSpot(access_token=os.environ["HUBSPOT_PRIVATE_APP_TOKEN"])
145
+ //
146
+ // contacts = client.crm.contacts.basic_api.get_page(
147
+ // limit=100,
148
+ // properties=["firstname", "lastname", "email"]
149
+ // )
150
+
151
+ ### Notes
152
+
153
+ - Private app tokens don't expire
154
+ - All private apps share daily rate limit
155
+ - Each private app has own burst limit
156
+ - Recommended: Rotate every 6 months
157
+
21
158
  ### CRM Object CRUD Operations
22
159
 
23
160
  Create, read, update, delete CRM records
24
161
 
25
- ## Anti-Patterns
162
+ **When to use**: Working with contacts, companies, deals, tickets
163
+
164
+ ### Template
165
+
166
+ import { Client } from "@hubspot/api-client";
167
+
168
+ const hubspotClient = new Client({
169
+ accessToken: process.env.HUBSPOT_TOKEN,
170
+ });
171
+
172
+ // CREATE contact
173
+ async function createContact(data: {
174
+ email: string;
175
+ firstname: string;
176
+ lastname: string;
177
+ }) {
178
+ const response = await hubspotClient.crm.contacts.basicApi.create({
179
+ properties: {
180
+ email: data.email,
181
+ firstname: data.firstname,
182
+ lastname: data.lastname,
183
+ },
184
+ });
185
+
186
+ return response;
187
+ }
188
+
189
+ // READ contact by ID
190
+ async function getContact(contactId: string) {
191
+ const response = await hubspotClient.crm.contacts.basicApi.getById(
192
+ contactId,
193
+ ["firstname", "lastname", "email", "phone", "company"],
194
+ );
195
+
196
+ return response;
197
+ }
198
+
199
+ // UPDATE contact
200
+ async function updateContact(contactId: string, properties: object) {
201
+ const response = await hubspotClient.crm.contacts.basicApi.update(
202
+ contactId,
203
+ { properties },
204
+ );
205
+
206
+ return response;
207
+ }
208
+
209
+ // DELETE contact
210
+ async function deleteContact(contactId: string) {
211
+ await hubspotClient.crm.contacts.basicApi.archive(contactId);
212
+ }
213
+
214
+ // SEARCH contacts
215
+ async function searchContacts(query: string) {
216
+ const response = await hubspotClient.crm.contacts.searchApi.doSearch({
217
+ query,
218
+ limit: 100,
219
+ properties: ["firstname", "lastname", "email"],
220
+ sorts: [{ propertyName: "createdate", direction: "DESCENDING" }],
221
+ });
222
+
223
+ return response.results;
224
+ }
225
+
226
+ // LIST with pagination
227
+ async function getAllContacts() {
228
+ const allContacts = [];
229
+ let after = undefined;
230
+
231
+ do {
232
+ const response = await hubspotClient.crm.contacts.basicApi.getPage(
233
+ 100,
234
+ after,
235
+ ["firstname", "lastname", "email"],
236
+ );
237
+
238
+ allContacts.push(...response.results);
239
+ after = response.paging?.next?.after;
240
+ } while (after);
241
+
242
+ return allContacts;
243
+ }
244
+
245
+ ### Notes
246
+
247
+ - Use properties param to fetch only needed fields
248
+ - Search API has 10k result limit
249
+ - Always implement pagination for lists
250
+ - Archive (soft delete) vs. GDPR delete available
251
+
252
+ ### Batch Operations
253
+
254
+ Bulk create, update, or read records efficiently
255
+
256
+ **When to use**: Processing multiple records (reduce rate limit usage)
257
+
258
+ ### Template
259
+
260
+ import { Client } from "@hubspot/api-client";
261
+
262
+ const hubspotClient = new Client({
263
+ accessToken: process.env.HUBSPOT_TOKEN,
264
+ });
265
+
266
+ // BATCH CREATE contacts (up to 100 per batch)
267
+ async function batchCreateContacts(contacts: Array<{
268
+ email: string;
269
+ firstname: string;
270
+ lastname: string;
271
+ }>) {
272
+ const inputs = contacts.map((contact) => ({
273
+ properties: {
274
+ email: contact.email,
275
+ firstname: contact.firstname,
276
+ lastname: contact.lastname,
277
+ },
278
+ }));
279
+
280
+ const response = await hubspotClient.crm.contacts.batchApi.create({
281
+ inputs,
282
+ });
283
+
284
+ return response.results;
285
+ }
286
+
287
+ // BATCH UPDATE contacts
288
+ async function batchUpdateContacts(
289
+ updates: Array<{ id: string; properties: object }>
290
+ ) {
291
+ const inputs = updates.map(({ id, properties }) => ({
292
+ id,
293
+ properties,
294
+ }));
295
+
296
+ const response = await hubspotClient.crm.contacts.batchApi.update({
297
+ inputs,
298
+ });
299
+
300
+ return response.results;
301
+ }
302
+
303
+ // BATCH READ contacts by ID
304
+ async function batchReadContacts(
305
+ ids: string[],
306
+ properties: string[] = ["firstname", "lastname", "email"]
307
+ ) {
308
+ const response = await hubspotClient.crm.contacts.batchApi.read({
309
+ inputs: ids.map((id) => ({ id })),
310
+ properties,
311
+ });
312
+
313
+ return response.results;
314
+ }
315
+
316
+ // BATCH ARCHIVE contacts
317
+ async function batchDeleteContacts(ids: string[]) {
318
+ await hubspotClient.crm.contacts.batchApi.archive({
319
+ inputs: ids.map((id) => ({ id })),
320
+ });
321
+ }
322
+
323
+ // Process large dataset in chunks
324
+ async function processLargeDataset(allContacts: any[]) {
325
+ const BATCH_SIZE = 100;
326
+ const results = [];
327
+
328
+ for (let i = 0; i < allContacts.length; i += BATCH_SIZE) {
329
+ const batch = allContacts.slice(i, i + BATCH_SIZE);
330
+ const batchResults = await batchCreateContacts(batch);
331
+ results.push(...batchResults);
332
+
333
+ // Respect rate limits - wait between batches
334
+ if (i + BATCH_SIZE < allContacts.length) {
335
+ await sleep(100); // 100ms between batches
336
+ }
337
+ }
338
+
339
+ return results;
340
+ }
341
+
342
+ ### Notes
343
+
344
+ - Max 100 items per batch request
345
+ - Saves up to 80% of rate limit quota
346
+ - Batch operations are atomic per item (partial success possible)
347
+ - Check response.errors for failed items
348
+
349
+ ### Associations v4 API
350
+
351
+ Create relationships between CRM records
352
+
353
+ **When to use**: Linking contacts to companies, deals, etc.
354
+
355
+ ### Template
356
+
357
+ import { Client, AssociationTypes } from "@hubspot/api-client";
358
+
359
+ const hubspotClient = new Client({
360
+ accessToken: process.env.HUBSPOT_TOKEN,
361
+ });
362
+
363
+ // CREATE association (Contact to Company)
364
+ async function associateContactToCompany(
365
+ contactId: string,
366
+ companyId: string
367
+ ) {
368
+ await hubspotClient.crm.associations.v4.basicApi.create(
369
+ "contacts",
370
+ contactId,
371
+ "companies",
372
+ companyId,
373
+ [
374
+ {
375
+ associationCategory: "HUBSPOT_DEFINED",
376
+ associationTypeId: AssociationTypes.contactToCompany,
377
+ },
378
+ ]
379
+ );
380
+ }
381
+
382
+ // CREATE association (Deal to Contact)
383
+ async function associateDealToContact(dealId: string, contactId: string) {
384
+ await hubspotClient.crm.associations.v4.basicApi.create(
385
+ "deals",
386
+ dealId,
387
+ "contacts",
388
+ contactId,
389
+ [
390
+ {
391
+ associationCategory: "HUBSPOT_DEFINED",
392
+ associationTypeId: 3, // deal_to_contact
393
+ },
394
+ ]
395
+ );
396
+ }
397
+
398
+ // GET associations for a record
399
+ async function getContactCompanies(contactId: string) {
400
+ const response = await hubspotClient.crm.associations.v4.basicApi.getPage(
401
+ "contacts",
402
+ contactId,
403
+ "companies",
404
+ undefined,
405
+ 500
406
+ );
407
+
408
+ return response.results;
409
+ }
410
+
411
+ // CREATE association with custom label
412
+ async function createLabeledAssociation(
413
+ contactId: string,
414
+ companyId: string,
415
+ labelId: number // Custom association label ID
416
+ ) {
417
+ await hubspotClient.crm.associations.v4.basicApi.create(
418
+ "contacts",
419
+ contactId,
420
+ "companies",
421
+ companyId,
422
+ [
423
+ {
424
+ associationCategory: "USER_DEFINED",
425
+ associationTypeId: labelId,
426
+ },
427
+ ]
428
+ );
429
+ }
430
+
431
+ // BATCH create associations
432
+ async function batchAssociateContactsToCompany(
433
+ contactIds: string[],
434
+ companyId: string
435
+ ) {
436
+ const inputs = contactIds.map((contactId) => ({
437
+ _from: { id: contactId },
438
+ to: { id: companyId },
439
+ types: [
440
+ {
441
+ associationCategory: "HUBSPOT_DEFINED",
442
+ associationTypeId: AssociationTypes.contactToCompany,
443
+ },
444
+ ],
445
+ }));
446
+
447
+ await hubspotClient.crm.associations.v4.batchApi.create(
448
+ "contacts",
449
+ "companies",
450
+ { inputs }
451
+ );
452
+ }
453
+
454
+ // Common association type IDs
455
+ // Contact to Company: 1
456
+ // Company to Contact: 2
457
+ // Deal to Contact: 3
458
+ // Contact to Deal: 4
459
+ // Deal to Company: 5
460
+ // Company to Deal: 6
461
+
462
+ ### Notes
463
+
464
+ - Requires SDK version 9.0.0+ for v4 API
465
+ - Association labels supported for custom relationships
466
+ - Use batch API for multiple associations
467
+ - HUBSPOT_DEFINED for standard, USER_DEFINED for custom labels
468
+
469
+ ### Webhook Handling
470
+
471
+ Receive real-time notifications from HubSpot
472
+
473
+ **When to use**: Need instant updates on CRM changes
474
+
475
+ ### Template
476
+
477
+ import crypto from "crypto";
478
+ import { Client } from "@hubspot/api-client";
479
+
480
+ // Webhook signature validation
481
+ function validateWebhookSignature(
482
+ requestBody: string,
483
+ signature: string,
484
+ clientSecret: string
485
+ ): boolean {
486
+ // For v2 signature (most common)
487
+ const expectedSignature = crypto
488
+ .createHmac("sha256", clientSecret)
489
+ .update(requestBody)
490
+ .digest("hex");
491
+
492
+ return signature === expectedSignature;
493
+ }
494
+
495
+ // Express webhook handler
496
+ app.post("/webhooks/hubspot", async (req, res) => {
497
+ const signature = req.headers["x-hubspot-signature-v3"] as string;
498
+ const timestamp = req.headers["x-hubspot-request-timestamp"] as string;
499
+ const requestBody = JSON.stringify(req.body);
500
+
501
+ // Validate signature
502
+ const isValid = validateWebhookSignature(
503
+ requestBody,
504
+ signature,
505
+ process.env.HUBSPOT_CLIENT_SECRET
506
+ );
26
507
 
27
- ### Using Deprecated API Keys
508
+ if (!isValid) {
509
+ console.error("Invalid webhook signature");
510
+ return res.status(401).send("Unauthorized");
511
+ }
28
512
 
29
- ### Individual Requests Instead of Batch
513
+ // Check timestamp (prevent replay attacks)
514
+ const timestampAge = Date.now() - parseInt(timestamp);
515
+ if (timestampAge > 300000) { // 5 minutes
516
+ console.error("Webhook timestamp too old");
517
+ return res.status(401).send("Timestamp expired");
518
+ }
30
519
 
31
- ### Polling Instead of Webhooks
520
+ // Process events - respond quickly!
521
+ const events = req.body;
32
522
 
33
- ## ⚠️ Sharp Edges
523
+ // Queue for async processing
524
+ for (const event of events) {
525
+ await queue.add("hubspot-webhook", event);
526
+ }
34
527
 
35
- | Issue | Severity | Solution |
36
- |-------|----------|----------|
37
- | Issue | high | See docs |
38
- | Issue | high | See docs |
39
- | Issue | critical | See docs |
40
- | Issue | high | See docs |
41
- | Issue | critical | See docs |
42
- | Issue | medium | See docs |
43
- | Issue | high | See docs |
44
- | Issue | medium | See docs |
528
+ // Respond immediately
529
+ res.status(200).send("OK");
530
+ });
531
+
532
+ // Async processor
533
+ async function processWebhookEvent(event: any) {
534
+ const { subscriptionType, objectId, propertyName, propertyValue } = event;
535
+
536
+ switch (subscriptionType) {
537
+ case "contact.creation":
538
+ await handleContactCreated(objectId);
539
+ break;
540
+
541
+ case "contact.propertyChange":
542
+ await handleContactPropertyChange(objectId, propertyName, propertyValue);
543
+ break;
544
+
545
+ case "deal.creation":
546
+ await handleDealCreated(objectId);
547
+ break;
548
+
549
+ case "contact.deletion":
550
+ await handleContactDeleted(objectId);
551
+ break;
552
+
553
+ default:
554
+ console.log(`Unhandled event: ${subscriptionType}`);
555
+ }
556
+ }
557
+
558
+ // Webhook subscription types:
559
+ // contact.creation, contact.deletion, contact.propertyChange
560
+ // company.creation, company.deletion, company.propertyChange
561
+ // deal.creation, deal.deletion, deal.propertyChange
562
+
563
+ ### Notes
564
+
565
+ - Validate signature before processing
566
+ - Respond within 5 seconds
567
+ - Queue heavy processing for async
568
+ - Max 1000 webhook subscriptions per app
569
+
570
+ ### Custom Objects
571
+
572
+ Create and manage custom object types
573
+
574
+ **When to use**: Standard objects don't fit your data model
575
+
576
+ ### Template
577
+
578
+ import { Client } from "@hubspot/api-client";
579
+
580
+ const hubspotClient = new Client({
581
+ accessToken: process.env.HUBSPOT_TOKEN,
582
+ });
583
+
584
+ // CREATE custom object schema
585
+ async function createCustomObjectSchema() {
586
+ const schema = {
587
+ name: "projects",
588
+ labels: {
589
+ singular: "Project",
590
+ plural: "Projects",
591
+ },
592
+ primaryDisplayProperty: "project_name",
593
+ requiredProperties: ["project_name"],
594
+ properties: [
595
+ {
596
+ name: "project_name",
597
+ label: "Project Name",
598
+ type: "string",
599
+ fieldType: "text",
600
+ },
601
+ {
602
+ name: "status",
603
+ label: "Status",
604
+ type: "enumeration",
605
+ fieldType: "select",
606
+ options: [
607
+ { label: "Active", value: "active" },
608
+ { label: "Completed", value: "completed" },
609
+ { label: "On Hold", value: "on_hold" },
610
+ ],
611
+ },
612
+ {
613
+ name: "budget",
614
+ label: "Budget",
615
+ type: "number",
616
+ fieldType: "number",
617
+ },
618
+ {
619
+ name: "start_date",
620
+ label: "Start Date",
621
+ type: "date",
622
+ fieldType: "date",
623
+ },
624
+ ],
625
+ associatedObjects: ["CONTACT", "COMPANY"],
626
+ };
627
+
628
+ const response = await hubspotClient.crm.schemas.coreApi.create(schema);
629
+ return response;
630
+ }
631
+
632
+ // CREATE custom object record
633
+ async function createProject(data: {
634
+ project_name: string;
635
+ status: string;
636
+ budget: number;
637
+ }) {
638
+ const response = await hubspotClient.crm.objects.basicApi.create(
639
+ "projects", // Custom object name
640
+ { properties: data }
641
+ );
642
+
643
+ return response;
644
+ }
645
+
646
+ // READ custom object by ID
647
+ async function getProject(projectId: string) {
648
+ const response = await hubspotClient.crm.objects.basicApi.getById(
649
+ "projects",
650
+ projectId,
651
+ ["project_name", "status", "budget", "start_date"]
652
+ );
653
+
654
+ return response;
655
+ }
656
+
657
+ // UPDATE custom object
658
+ async function updateProject(projectId: string, properties: object) {
659
+ const response = await hubspotClient.crm.objects.basicApi.update(
660
+ "projects",
661
+ projectId,
662
+ { properties }
663
+ );
664
+
665
+ return response;
666
+ }
667
+
668
+ // SEARCH custom objects
669
+ async function searchProjects(status: string) {
670
+ const response = await hubspotClient.crm.objects.searchApi.doSearch(
671
+ "projects",
672
+ {
673
+ filterGroups: [
674
+ {
675
+ filters: [
676
+ {
677
+ propertyName: "status",
678
+ operator: "EQ",
679
+ value: status,
680
+ },
681
+ ],
682
+ },
683
+ ],
684
+ properties: ["project_name", "status", "budget"],
685
+ limit: 100,
686
+ }
687
+ );
688
+
689
+ return response.results;
690
+ }
691
+
692
+ ### Notes
693
+
694
+ - Custom objects require Enterprise tier
695
+ - Max 10 custom objects per account
696
+ - Use crm.objects API with object name as parameter
697
+ - Can associate with standard and other custom objects
698
+
699
+ ## Sharp Edges
700
+
701
+ ### Rate Limits Vary by App Type and Hub Tier
702
+
703
+ Severity: HIGH
704
+
705
+ ### 5% Error Rate Threshold for Marketplace Apps
706
+
707
+ Severity: HIGH
708
+
709
+ ### API Keys Deprecated - Use OAuth or Private App Tokens
710
+
711
+ Severity: CRITICAL
712
+
713
+ ### OAuth Access Tokens Expire in 30 Minutes
714
+
715
+ Severity: HIGH
716
+
717
+ ### Webhook Requests Must Be Validated
718
+
719
+ Severity: CRITICAL
720
+
721
+ ### All List Endpoints Require Pagination
722
+
723
+ Severity: MEDIUM
724
+
725
+ ### Associations v4 API Has Breaking Changes
726
+
727
+ Severity: HIGH
728
+
729
+ ### Polling Limited to 100,000 Requests Per Day
730
+
731
+ Severity: MEDIUM
732
+
733
+ ## Validation Checks
734
+
735
+ ### Hardcoded HubSpot API Key
736
+
737
+ Severity: ERROR
738
+
739
+ API keys must never be hardcoded
740
+
741
+ Message: Hardcoded HubSpot API key detected. Use environment variables. Note: API keys are deprecated - use Private App tokens.
742
+
743
+ ### Hardcoded HubSpot Access Token
744
+
745
+ Severity: ERROR
746
+
747
+ Access tokens must use environment variables
748
+
749
+ Message: Hardcoded HubSpot access token. Use environment variables.
750
+
751
+ ### Hardcoded Client Secret
752
+
753
+ Severity: ERROR
754
+
755
+ OAuth client secrets must be secured
756
+
757
+ Message: Hardcoded client secret. Use environment variables.
758
+
759
+ ### Missing Webhook Signature Validation
760
+
761
+ Severity: ERROR
762
+
763
+ Webhook endpoints must validate HubSpot signatures
764
+
765
+ Message: Webhook endpoint without signature validation. Validate X-HubSpot-Signature-v3.
766
+
767
+ ### Missing Rate Limit Handling
768
+
769
+ Severity: WARNING
770
+
771
+ API calls should handle 429 responses
772
+
773
+ Message: HubSpot API calls without rate limit handling. Implement retry logic with backoff.
774
+
775
+ ### Unthrottled Parallel API Calls
776
+
777
+ Severity: WARNING
778
+
779
+ Parallel calls can exceed rate limits
780
+
781
+ Message: Parallel HubSpot API calls without throttling. Use rate limiter.
782
+
783
+ ### Missing Pagination for List Calls
784
+
785
+ Severity: WARNING
786
+
787
+ List endpoints return paginated results
788
+
789
+ Message: API call without pagination handling. Implement cursor-based pagination.
790
+
791
+ ### Individual Operations in Loop
792
+
793
+ Severity: INFO
794
+
795
+ Use batch operations for multiple items
796
+
797
+ Message: Individual API calls in loop. Consider batch operations for better performance.
798
+
799
+ ### Token Storage Without Expiry
800
+
801
+ Severity: WARNING
802
+
803
+ OAuth tokens expire and need refresh logic
804
+
805
+ Message: Token storage without expiry tracking. Store expiresAt for refresh logic.
806
+
807
+ ### Deprecated API Key Usage
808
+
809
+ Severity: ERROR
810
+
811
+ API keys are deprecated
812
+
813
+ Message: Using deprecated API key. Migrate to Private App token or OAuth 2.0.
814
+
815
+ ## Collaboration
816
+
817
+ ### Delegation Triggers
818
+
819
+ - user needs email marketing automation -> email-marketing (Beyond HubSpot's built-in email tools)
820
+ - user needs custom CRM UI -> frontend (Building portal or dashboard)
821
+ - user needs data pipeline -> data-engineer (ETL from HubSpot to warehouse)
822
+ - user needs Salesforce integration -> salesforce-development (HubSpot + Salesforce sync)
823
+ - user needs payment processing -> stripe-integration (Payments beyond HubSpot quotes)
824
+ - user needs analytics dashboard -> analytics-specialist (Custom reporting beyond HubSpot)
45
825
 
46
826
  ## When to Use
47
- This skill is applicable to execute the workflow or actions described in the overview.
827
+
828
+ - User mentions or implies: hubspot
829
+ - User mentions or implies: hubspot api
830
+ - User mentions or implies: hubspot crm
831
+ - User mentions or implies: hubspot integration
832
+ - User mentions or implies: contacts api