linkedin-secret-sauce 0.12.4 → 0.13.0

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 (161) hide show
  1. package/dist/enrichment/matching.js +161 -81
  2. package/dist/enrichment/providers/bounceban.js +19 -10
  3. package/dist/enrichment/providers/construct.js +97 -89
  4. package/dist/enrichment/providers/cosiall.js +2 -2
  5. package/dist/enrichment/providers/hunter.js +2 -1
  6. package/dist/enrichment/providers/ldd.js +4 -2
  7. package/dist/enrichment/providers/smartprospect.d.ts +5 -2
  8. package/dist/enrichment/providers/smartprospect.js +64 -27
  9. package/dist/enrichment/providers/snovio.js +7 -3
  10. package/dist/enrichment/providers/trykitt.d.ts +2 -2
  11. package/dist/enrichment/providers/trykitt.js +86 -21
  12. package/dist/index.d.ts +1 -1
  13. package/dist/linkedin-api.d.ts +113 -1
  14. package/dist/linkedin-api.js +836 -3
  15. package/dist/parsers/profile-parser.js +1 -0
  16. package/dist/types.d.ts +164 -0
  17. package/docs/api/assets/hierarchy.js +1 -1
  18. package/docs/api/assets/navigation.js +1 -1
  19. package/docs/api/assets/search.js +1 -1
  20. package/docs/api/classes/LinkedInClientError.html +4 -4
  21. package/docs/api/functions/_testGetAccountCookies.html +2 -2
  22. package/docs/api/functions/_testGetAccountEntry.html +2 -2
  23. package/docs/api/functions/_testGetAllAccountIds.html +2 -2
  24. package/docs/api/functions/_testGetPoolState.html +2 -2
  25. package/docs/api/functions/adminResetAccount.html +1 -1
  26. package/docs/api/functions/adminSetCooldown.html +1 -1
  27. package/docs/api/functions/analyzeProfilePosts.html +14 -0
  28. package/docs/api/functions/buildCookieHeader.html +1 -1
  29. package/docs/api/functions/clearAllSmartLeadTokens.html +2 -2
  30. package/docs/api/functions/clearRequestHistory.html +1 -1
  31. package/docs/api/functions/clearSessionAccount.html +1 -1
  32. package/docs/api/functions/clearSmartLeadToken.html +2 -2
  33. package/docs/api/functions/createEnrichmentClient.html +2 -2
  34. package/docs/api/functions/extractCsrfToken.html +1 -1
  35. package/docs/api/functions/extractLinkedInHandle.html +2 -2
  36. package/docs/api/functions/fetchCookiesFromCosiall.html +2 -2
  37. package/docs/api/functions/fetchProfileEmailsFromCosiall.html +2 -2
  38. package/docs/api/functions/forceRefreshCookies.html +1 -1
  39. package/docs/api/functions/getAccountForSession.html +1 -1
  40. package/docs/api/functions/getAccountsSummary.html +1 -1
  41. package/docs/api/functions/getCompaniesBatch.html +2 -2
  42. package/docs/api/functions/getCompanyById.html +2 -2
  43. package/docs/api/functions/getCompanyByUrl.html +1 -1
  44. package/docs/api/functions/getConfig.html +1 -1
  45. package/docs/api/functions/getCookiePoolHealth.html +1 -1
  46. package/docs/api/functions/getPostComments.html +11 -0
  47. package/docs/api/functions/getPostHistory.html +12 -0
  48. package/docs/api/functions/getPostReactions.html +12 -0
  49. package/docs/api/functions/getProfileByUrn.html +2 -2
  50. package/docs/api/functions/getProfileByVanity.html +2 -2
  51. package/docs/api/functions/getProfilesBatch.html +1 -1
  52. package/docs/api/functions/getRequestHistory.html +1 -1
  53. package/docs/api/functions/getSalesNavigatorProfileDetails.html +1 -1
  54. package/docs/api/functions/getSalesNavigatorProfileFull.html +2 -2
  55. package/docs/api/functions/getSmartLeadToken.html +1 -1
  56. package/docs/api/functions/getSmartLeadTokenCacheStats.html +2 -2
  57. package/docs/api/functions/getSmartLeadUser.html +2 -2
  58. package/docs/api/functions/getSnapshot.html +1 -1
  59. package/docs/api/functions/getYearsAtCompanyOptions.html +2 -2
  60. package/docs/api/functions/getYearsInPositionOptions.html +2 -2
  61. package/docs/api/functions/getYearsOfExperienceOptions.html +2 -2
  62. package/docs/api/functions/incrementMetric.html +1 -1
  63. package/docs/api/functions/initializeCookiePool.html +1 -1
  64. package/docs/api/functions/initializeLinkedInClient.html +1 -1
  65. package/docs/api/functions/isBusinessEmail.html +2 -2
  66. package/docs/api/functions/isDisposableDomain.html +2 -2
  67. package/docs/api/functions/isDisposableEmail.html +2 -2
  68. package/docs/api/functions/isPersonalDomain.html +2 -2
  69. package/docs/api/functions/isPersonalEmail.html +2 -2
  70. package/docs/api/functions/isRoleAccount.html +2 -2
  71. package/docs/api/functions/isValidEmailSyntax.html +2 -2
  72. package/docs/api/functions/parseFullProfile.html +2 -2
  73. package/docs/api/functions/parseSalesSearchResults.html +1 -1
  74. package/docs/api/functions/reportAccountFailure.html +1 -1
  75. package/docs/api/functions/reportAccountSuccess.html +1 -1
  76. package/docs/api/functions/resolveCompanyUniversalName.html +1 -1
  77. package/docs/api/functions/searchSalesLeads.html +2 -2
  78. package/docs/api/functions/selectAccountForRequest.html +1 -1
  79. package/docs/api/functions/setAccountForSession.html +1 -1
  80. package/docs/api/functions/typeahead.html +1 -1
  81. package/docs/api/functions/verifyEmailMx.html +1 -1
  82. package/docs/api/hierarchy.html +1 -1
  83. package/docs/api/index.html +2 -2
  84. package/docs/api/interfaces/AccountCookies.html +2 -2
  85. package/docs/api/interfaces/AnalyzedPost.html +8 -0
  86. package/docs/api/interfaces/BatchEnrichmentOptions.html +8 -8
  87. package/docs/api/interfaces/CacheAdapter.html +4 -4
  88. package/docs/api/interfaces/CanonicalEmail.html +8 -8
  89. package/docs/api/interfaces/CommentAuthor.html +8 -0
  90. package/docs/api/interfaces/Company.html +2 -2
  91. package/docs/api/interfaces/ConstructConfig.html +5 -5
  92. package/docs/api/interfaces/CosiallProfileEmailsResponse.html +6 -6
  93. package/docs/api/interfaces/EnrichmentCandidate.html +4 -4
  94. package/docs/api/interfaces/EnrichmentClient.html +6 -6
  95. package/docs/api/interfaces/EnrichmentClientConfig.html +7 -7
  96. package/docs/api/interfaces/EnrichmentLogger.html +3 -3
  97. package/docs/api/interfaces/EnrichmentOptions.html +6 -6
  98. package/docs/api/interfaces/HunterConfig.html +3 -3
  99. package/docs/api/interfaces/LddConfig.html +3 -3
  100. package/docs/api/interfaces/LddProfileData.html +2 -2
  101. package/docs/api/interfaces/LinkedInClientConfig.html +2 -2
  102. package/docs/api/interfaces/LinkedInCookie.html +2 -2
  103. package/docs/api/interfaces/LinkedInPosition.html +2 -2
  104. package/docs/api/interfaces/LinkedInProfile.html +2 -2
  105. package/docs/api/interfaces/LinkedInSpotlightBadge.html +2 -2
  106. package/docs/api/interfaces/LinkedInTenure.html +2 -2
  107. package/docs/api/interfaces/Metrics.html +2 -2
  108. package/docs/api/interfaces/MetricsSnapshot.html +2 -2
  109. package/docs/api/interfaces/PostAnalytics.html +11 -0
  110. package/docs/api/interfaces/PostComment.html +8 -0
  111. package/docs/api/interfaces/PostCommentsResult.html +7 -0
  112. package/docs/api/interfaces/PostHistoryResult.html +4 -0
  113. package/docs/api/interfaces/PostReaction.html +5 -0
  114. package/docs/api/interfaces/PostReactionsResult.html +5 -0
  115. package/docs/api/interfaces/ProfileAnalysisOptions.html +14 -0
  116. package/docs/api/interfaces/ProfileAnalysisResult.html +9 -0
  117. package/docs/api/interfaces/ProfileEducation.html +2 -2
  118. package/docs/api/interfaces/ProfileEmailsLookupOptions.html +5 -5
  119. package/docs/api/interfaces/ProfilePosition.html +2 -2
  120. package/docs/api/interfaces/ProfilePost.html +14 -0
  121. package/docs/api/interfaces/ProfileSkill.html +2 -2
  122. package/docs/api/interfaces/ProviderResult.html +6 -6
  123. package/docs/api/interfaces/ProvidersConfig.html +6 -6
  124. package/docs/api/interfaces/ReactionActor.html +9 -0
  125. package/docs/api/interfaces/RequestHistoryEntry.html +2 -2
  126. package/docs/api/interfaces/SalesLeadSearchResult.html +2 -2
  127. package/docs/api/interfaces/SalesNavigatorContactInfo.html +2 -2
  128. package/docs/api/interfaces/SalesNavigatorPosition.html +2 -2
  129. package/docs/api/interfaces/SalesNavigatorProfile.html +2 -2
  130. package/docs/api/interfaces/SalesNavigatorProfileFull.html +4 -4
  131. package/docs/api/interfaces/SearchSalesResult.html +2 -2
  132. package/docs/api/interfaces/SmartLeadAuthConfig.html +4 -4
  133. package/docs/api/interfaces/SmartLeadCredentials.html +2 -2
  134. package/docs/api/interfaces/SmartLeadLoginResponse.html +2 -2
  135. package/docs/api/interfaces/SmartLeadUser.html +2 -2
  136. package/docs/api/interfaces/SmartProspectConfig.html +8 -8
  137. package/docs/api/interfaces/SmartProspectContact.html +2 -2
  138. package/docs/api/interfaces/SmartProspectSearchFilters.html +21 -21
  139. package/docs/api/interfaces/TypeaheadItem.html +2 -2
  140. package/docs/api/interfaces/TypeaheadResult.html +2 -2
  141. package/docs/api/interfaces/VerificationResult.html +9 -9
  142. package/docs/api/types/CostCallback.html +2 -2
  143. package/docs/api/types/Geo.html +2 -2
  144. package/docs/api/types/LddApiResponse.html +1 -1
  145. package/docs/api/types/LinkedInReactionType.html +2 -0
  146. package/docs/api/types/ProviderFunc.html +2 -2
  147. package/docs/api/types/ProviderName.html +2 -2
  148. package/docs/api/types/SalesSearchFilters.html +2 -2
  149. package/docs/api/types/TypeaheadType.html +1 -1
  150. package/docs/api/variables/COMPANY_SIZE_OPTIONS.html +1 -1
  151. package/docs/api/variables/DEFAULT_PROVIDER_ORDER.html +2 -2
  152. package/docs/api/variables/DISPOSABLE_DOMAINS.html +2 -2
  153. package/docs/api/variables/FUNCTION_OPTIONS.html +1 -1
  154. package/docs/api/variables/INDUSTRY_OPTIONS.html +1 -1
  155. package/docs/api/variables/LANGUAGE_OPTIONS.html +1 -1
  156. package/docs/api/variables/PERSONAL_DOMAINS.html +2 -2
  157. package/docs/api/variables/PROVIDER_COSTS.html +2 -2
  158. package/docs/api/variables/REGION_OPTIONS.html +1 -1
  159. package/docs/api/variables/SENIORITY_OPTIONS.html +2 -2
  160. package/docs/api/variables/YEARS_OPTIONS.html +1 -1
  161. package/package.json +1 -1
@@ -148,7 +148,8 @@ function createHunterProvider(config) {
148
148
  }
149
149
  return null;
150
150
  }
151
- catch {
151
+ catch (err) {
152
+ console.error("[Hunter] Email lookup failed:", err instanceof Error ? err.message : err);
152
153
  return null;
153
154
  }
154
155
  }
@@ -52,7 +52,8 @@ function createLddProvider(config) {
52
52
  const response = await (0, http_retry_1.getWithRetry)(endpoint, { Authorization: `Bearer ${apiToken}` }, { retries: 1, backoffMs: 100 });
53
53
  return parseResponse(response);
54
54
  }
55
- catch {
55
+ catch (err) {
56
+ console.error("[LDD] Lookup by numeric ID failed:", err instanceof Error ? err.message : err);
56
57
  return null;
57
58
  }
58
59
  }
@@ -62,7 +63,8 @@ function createLddProvider(config) {
62
63
  const response = await (0, http_retry_1.getWithRetry)(endpoint, { Authorization: `Bearer ${apiToken}` }, { retries: 1, backoffMs: 100 });
63
64
  return parseResponse(response);
64
65
  }
65
- catch {
66
+ catch (err) {
67
+ console.error("[LDD] Lookup by username failed:", err instanceof Error ? err.message : err);
66
68
  return null;
67
69
  }
68
70
  }
@@ -27,8 +27,11 @@ export interface SmartProspectLocationOptions {
27
27
  export interface SmartProspectClient {
28
28
  /** Search for contacts (FREE - no credits used) */
29
29
  search: (filters: SmartProspectSearchFilters) => Promise<SmartProspectSearchResponse>;
30
- /** Fetch/enrich emails for specific contact IDs (COSTS CREDITS) */
31
- fetch: (contactIds: string[]) => Promise<SmartProspectFetchResponse>;
30
+ /** Fetch/enrich emails for specific contact IDs (COSTS CREDITS)
31
+ * @param contactIds - Array of contact IDs to fetch
32
+ * @param searchFilterId - Filter ID from the search response (required by API)
33
+ */
34
+ fetch: (contactIds: string[], searchFilterId?: number) => Promise<SmartProspectFetchResponse>;
32
35
  /** Search and immediately fetch all results (COSTS CREDITS for fetched contacts) */
33
36
  searchAndFetch: (filters: SmartProspectSearchFilters) => Promise<{
34
37
  searchResponse: SmartProspectSearchResponse;
@@ -22,10 +22,10 @@ const candidate_parser_1 = require("../utils/candidate-parser");
22
22
  const noop_provider_1 = require("../utils/noop-provider");
23
23
  const DEFAULT_API_URL = "https://prospect-api.smartlead.ai/api/search-email-leads";
24
24
  const DEFAULT_POLLING_CONFIG = {
25
- initialDelay: 500,
26
- pollInterval: 1000,
27
- maxAttempts: 10,
28
- maxWaitTime: 15000,
25
+ initialDelay: 1000,
26
+ pollInterval: 2000,
27
+ maxAttempts: 45,
28
+ maxWaitTime: 90000,
29
29
  };
30
30
  /**
31
31
  * HTTP request with retry on 429 rate limit
@@ -37,12 +37,15 @@ async function requestWithRetry(url, options, retries = 2, backoffMs = 300) {
37
37
  const res = await fetch(url, options);
38
38
  // Retry on rate limit
39
39
  if (res.status === 429 && i < retries) {
40
+ console.error("[SmartProspect] Rate limited, retrying...");
40
41
  await (0, http_retry_1.delay)(backoffMs * Math.pow(2, i));
41
42
  continue;
42
43
  }
43
44
  if (!res.ok) {
44
45
  const errorText = await res.text().catch(() => "");
45
- throw new Error(`SmartProspect API error: ${res.status} - ${errorText}`);
46
+ const error = new Error(`SmartProspect API error: ${res.status} - ${errorText}`);
47
+ console.error("[SmartProspect] API error:", error.message);
48
+ throw error;
46
49
  }
47
50
  return (await res.json());
48
51
  }
@@ -54,6 +57,7 @@ async function requestWithRetry(url, options, retries = 2, backoffMs = 300) {
54
57
  }
55
58
  }
56
59
  }
60
+ console.error("[SmartProspect] Request failed after retries:", lastErr instanceof Error ? lastErr.message : lastErr);
57
61
  throw lastErr ?? new Error("smartprospect_http_error");
58
62
  }
59
63
  /**
@@ -155,7 +159,7 @@ function createSmartProspectProvider(config) {
155
159
  },
156
160
  body: JSON.stringify({
157
161
  ...filters,
158
- dontDisplayOwnedContact: filters.dontDisplayOwnedContact ?? true,
162
+ dontDisplayOwnedContact: filters.dontDisplayOwnedContact ?? false,
159
163
  limit: filters.limit ?? 10,
160
164
  titleExactMatch: filters.titleExactMatch ?? false,
161
165
  }),
@@ -264,9 +268,20 @@ function createSmartProspectProvider(config) {
264
268
  * 1. Call fetch-contacts (returns immediately with status: "pending")
265
269
  * 2. Poll get-contacts until completed
266
270
  * 3. Return enriched contacts with emails
271
+ *
272
+ * @param contactIds - Array of contact IDs to fetch
273
+ * @param searchFilterId - Filter ID from the search response (required by API)
267
274
  */
268
- async function fetchContacts(contactIds) {
275
+ async function fetchContacts(contactIds, searchFilterId) {
269
276
  const makeFetchRequest = async (token) => {
277
+ // API expects: { filter_id, limit, visual_limit, id: [...] }
278
+ // filter_id comes from search response and is required for polling
279
+ const fetchPayload = {
280
+ filter_id: searchFilterId,
281
+ limit: contactIds.length,
282
+ visual_limit: contactIds.length,
283
+ id: contactIds,
284
+ };
270
285
  return requestWithRetry(`${apiUrl}/fetch-contacts`, {
271
286
  method: "POST",
272
287
  headers: {
@@ -274,17 +289,17 @@ function createSmartProspectProvider(config) {
274
289
  "Content-Type": "application/json",
275
290
  Accept: "application/json",
276
291
  },
277
- body: JSON.stringify({ contactIds }),
292
+ body: JSON.stringify(fetchPayload),
278
293
  });
279
294
  };
280
295
  try {
281
296
  const token = await getAuthToken();
282
297
  const fetchResult = await makeFetchRequest(token);
283
- // If fetch-contacts returns pending status with filter_id, poll for results
284
- const filterId = fetchResult.data?.filter_id;
298
+ // If fetch-contacts returns pending status, poll for results using the filter_id we sent
285
299
  const status = fetchResult.data?.status;
286
- if (filterId && status === "pending") {
287
- const pollResult = await pollForContactResults(filterId, contactIds.length);
300
+ // Use the searchFilterId we passed in (from search response), not from fetch response
301
+ if (searchFilterId && status === "pending") {
302
+ const pollResult = await pollForContactResults(searchFilterId, contactIds.length);
288
303
  if (pollResult.success && pollResult.data.list.length > 0) {
289
304
  return {
290
305
  success: true,
@@ -296,7 +311,7 @@ function createSmartProspectProvider(config) {
296
311
  leads_found: pollResult.data.metrics.totalContacts,
297
312
  email_fetched: pollResult.data.metrics.totalEmails,
298
313
  verification_status_list: [],
299
- filter_id: filterId,
314
+ filter_id: String(searchFilterId),
300
315
  status: "completed",
301
316
  },
302
317
  };
@@ -394,7 +409,8 @@ function createSmartProspectProvider(config) {
394
409
  // Step 2: Fetch emails for ALL matching contacts (COSTS CREDITS)
395
410
  // Note: This costs more credits but gives us all available emails
396
411
  const contactIds = scoredMatches.map((m) => m.contact.id);
397
- const fetchResult = await fetchContacts(contactIds);
412
+ const searchFilterId = searchResult.data.filter_id;
413
+ const fetchResult = await fetchContacts(contactIds, searchFilterId);
398
414
  if (!fetchResult.success || fetchResult.data.list.length === 0) {
399
415
  return null;
400
416
  }
@@ -549,7 +565,7 @@ function createSmartProspectClient(config) {
549
565
  if (filters.city?.length)
550
566
  payload.city = filters.city;
551
567
  // Workflow options
552
- payload.dontDisplayOwnedContact = filters.dontDisplayOwnedContact ?? true;
568
+ payload.dontDisplayOwnedContact = filters.dontDisplayOwnedContact ?? false;
553
569
  // Pagination
554
570
  payload.limit = filters.limit ?? 25;
555
571
  if (filters.scroll_id)
@@ -628,11 +644,18 @@ function createSmartProspectClient(config) {
628
644
  const token = await getAuthToken();
629
645
  const result = await makeRequest(token);
630
646
  if (result.success && result.data.metrics) {
631
- const { completed, totalContacts } = result.data.metrics;
632
- // Check if all contacts have been processed
633
- if (completed >= totalContacts || completed >= expectedCount) {
647
+ const { completed, totalContacts, totalEmails } = result.data.metrics;
648
+ const contact = result.data.list[0];
649
+ const email = contact?.email;
650
+ console.log(`[SmartProspect] poll ${attempt + 1}/${maxAttempts}: completed=${completed}/${totalContacts}, emails=${totalEmails}, email=${email || "(none)"}`);
651
+ // Check if all contacts have been processed AND we have an email
652
+ if ((completed >= totalContacts || completed >= expectedCount) &&
653
+ email) {
634
654
  return result;
635
655
  }
656
+ // NOTE: Don't exit early on noEmailFound - it may be a temporary state
657
+ // The API sometimes reports noEmailFound before the email is ready
658
+ // Keep polling until we get an email or hit the timeout
636
659
  }
637
660
  // Wait before next poll
638
661
  await (0, http_retry_1.delay)(pollInterval);
@@ -680,8 +703,11 @@ function createSmartProspectClient(config) {
680
703
  * 1. Call fetch-contacts to initiate lookup (returns immediately with status: "pending")
681
704
  * 2. Poll get-contacts until metrics.completed >= expected count
682
705
  * 3. Return the final enriched contacts with emails
706
+ *
707
+ * @param contactIds - Array of contact IDs to fetch
708
+ * @param searchFilterId - Filter ID from the search response (required by API)
683
709
  */
684
- async function fetch(contactIds) {
710
+ async function fetch(contactIds, searchFilterId) {
685
711
  if (!contactIds.length) {
686
712
  return {
687
713
  success: true,
@@ -705,6 +731,13 @@ function createSmartProspectClient(config) {
705
731
  };
706
732
  }
707
733
  const makeFetchRequest = async (token) => {
734
+ // API expects: { filter_id, limit, visual_limit, id: [...] }
735
+ const fetchPayload = {
736
+ filter_id: searchFilterId,
737
+ limit: contactIds.length,
738
+ visual_limit: contactIds.length,
739
+ id: contactIds,
740
+ };
708
741
  return requestWithRetry(`${apiUrl}/fetch-contacts`, {
709
742
  method: "POST",
710
743
  headers: {
@@ -712,18 +745,21 @@ function createSmartProspectClient(config) {
712
745
  "Content-Type": "application/json",
713
746
  Accept: "application/json",
714
747
  },
715
- body: JSON.stringify({ contactIds }),
748
+ body: JSON.stringify(fetchPayload),
716
749
  });
717
750
  };
718
751
  try {
719
752
  const token = await getAuthToken();
720
753
  const fetchResult = await makeFetchRequest(token);
721
- // If fetch-contacts returns a filter_id with pending status, we need to poll
722
- const filterId = fetchResult.data?.filter_id;
723
- const status = fetchResult.data?.status;
724
- if (filterId && status === "pending") {
754
+ // If fetch-contacts returns pending status OR no email found, poll for results
755
+ // Note: status can be in data.status OR data.list[0].status depending on API response
756
+ const status = fetchResult.data?.status || fetchResult.data?.list?.[0]?.status;
757
+ const hasEmail = fetchResult.data?.list?.[0]?.email;
758
+ // Use the searchFilterId we passed in (from search response), not from fetch response
759
+ // Poll if: status is pending, OR we have a filter_id but no email yet
760
+ if (searchFilterId && (status === "pending" || !hasEmail)) {
725
761
  // Poll get-contacts for final results
726
- const pollResult = await pollForResults(filterId, contactIds.length);
762
+ const pollResult = await pollForResults(searchFilterId, contactIds.length);
727
763
  if (pollResult.success && pollResult.data.list.length > 0) {
728
764
  // Convert poll result to fetch response format
729
765
  return {
@@ -736,7 +772,7 @@ function createSmartProspectClient(config) {
736
772
  leads_found: pollResult.data.metrics.totalContacts,
737
773
  email_fetched: pollResult.data.metrics.totalEmails,
738
774
  verification_status_list: [],
739
- filter_id: filterId,
775
+ filter_id: String(searchFilterId),
740
776
  status: "completed",
741
777
  },
742
778
  };
@@ -793,7 +829,8 @@ function createSmartProspectClient(config) {
793
829
  };
794
830
  }
795
831
  const contactIds = searchResponse.data.list.map((c) => c.id);
796
- const fetchResponse = await fetch(contactIds);
832
+ const searchFilterId = searchResponse.data.filter_id;
833
+ const fetchResponse = await fetch(contactIds, searchFilterId);
797
834
  return {
798
835
  searchResponse,
799
836
  fetchResponse,
@@ -97,7 +97,8 @@ async function getAccessToken(clientId, clientSecret, apiUrl, timeoutMs) {
97
97
  tokenExpiresAt = Date.now() + (data.expires_in - 300) * 1000;
98
98
  return data.access_token;
99
99
  }
100
- catch {
100
+ catch (err) {
101
+ console.error("[Snov.io] Failed to get access token:", err instanceof Error ? err.message : err);
101
102
  return null;
102
103
  }
103
104
  finally {
@@ -136,8 +137,10 @@ async function findEmailsByName(firstName, lastName, domain, accessToken, apiUrl
136
137
  }
137
138
  catch (error) {
138
139
  if (error instanceof Error && error.name === "AbortError") {
139
- return null; // Timeout
140
+ console.error("[Snov.io] Request timed out");
141
+ return null;
140
142
  }
143
+ console.error("[Snov.io] Email lookup failed:", error instanceof Error ? error.message : error);
141
144
  throw error;
142
145
  }
143
146
  finally {
@@ -268,7 +271,8 @@ async function verifyEmailWithSnovio(email, config) {
268
271
  status: result.result,
269
272
  };
270
273
  }
271
- catch {
274
+ catch (err) {
275
+ console.error("[Snov.io] Email verification failed:", err instanceof Error ? err.message : err);
272
276
  return null;
273
277
  }
274
278
  finally {
@@ -11,8 +11,8 @@
11
11
  * - 2-5x faster than traditional SMTP verification
12
12
  *
13
13
  * API Endpoints:
14
- * - POST /job/find-email - Find email by name + domain
15
- * - POST /job/verify-email - Verify existing email
14
+ * - POST /job/find_email - Find email by name + domain
15
+ * - POST /job/verify_email - Verify existing email
16
16
  *
17
17
  * Rate Limits (Free tier):
18
18
  * - 2 requests/second
@@ -12,8 +12,8 @@
12
12
  * - 2-5x faster than traditional SMTP verification
13
13
  *
14
14
  * API Endpoints:
15
- * - POST /job/find-email - Find email by name + domain
16
- * - POST /job/verify-email - Verify existing email
15
+ * - POST /job/find_email - Find email by name + domain
16
+ * - POST /job/verify_email - Verify existing email
17
17
  *
18
18
  * Rate Limits (Free tier):
19
19
  * - 2 requests/second
@@ -31,6 +31,58 @@ const candidate_parser_1 = require("../utils/candidate-parser");
31
31
  const noop_provider_1 = require("../utils/noop-provider");
32
32
  const DEFAULT_API_URL = "https://api.trykitt.ai";
33
33
  const DEFAULT_TIMEOUT_MS = 30000;
34
+ const JOB_POLL_INTERVAL_MS = 1000;
35
+ const JOB_MAX_POLLS = 30; // Max 30 seconds waiting
36
+ /**
37
+ * Poll for job completion
38
+ */
39
+ async function pollJobResult(apiUrl, apiKey, jobId, timeoutMs) {
40
+ for (let i = 0; i < JOB_MAX_POLLS; i++) {
41
+ try {
42
+ const response = await (0, http_retry_1.getWithRetry)(`${apiUrl}/job?id=${jobId}`, {
43
+ "x-api-key": apiKey,
44
+ }, {
45
+ retries: 1,
46
+ backoffMs: 500,
47
+ timeoutMs,
48
+ });
49
+ // API returns an array - get first element
50
+ const jobData = Array.isArray(response)
51
+ ? response[0]
52
+ : response;
53
+ if (!jobData) {
54
+ return null;
55
+ }
56
+ const jobObj = jobData;
57
+ const results = jobObj.results;
58
+ if (jobObj.status === "completed") {
59
+ // Return in expected format, mapping 'results' to 'result'
60
+ return {
61
+ status: jobObj.status,
62
+ result: results
63
+ ? {
64
+ email: results.email !== "no-results-found"
65
+ ? results.email
66
+ : null,
67
+ confidence: results.confidence,
68
+ confidence_score: results.confidence_score,
69
+ }
70
+ : null,
71
+ };
72
+ }
73
+ if (jobObj.status === "failed" || jobObj.status === "error") {
74
+ return null;
75
+ }
76
+ // Wait before next poll
77
+ await new Promise((resolve) => setTimeout(resolve, JOB_POLL_INTERVAL_MS));
78
+ }
79
+ catch (err) {
80
+ console.error("[TryKitt] Poll error:", err instanceof Error ? err.message : err);
81
+ return null;
82
+ }
83
+ }
84
+ return null;
85
+ }
34
86
  /**
35
87
  * Extract TryKitt-specific inputs from candidate using shared parser
36
88
  */
@@ -82,25 +134,32 @@ function createTryKittProvider(config) {
82
134
  return null;
83
135
  }
84
136
  try {
85
- // Build request body
137
+ // Build request body - API expects camelCase parameters
138
+ // callbackType: "here" forces synchronous/realtime processing
86
139
  const body = {
87
- full_name: fullName,
88
- domain: domain,
140
+ fullName: fullName,
141
+ domainOrWebsite: domain,
142
+ callbackType: "here",
143
+ callbackURL: "",
89
144
  };
90
145
  // Add LinkedIn URL if available (improves accuracy)
91
146
  if (linkedinUrl) {
92
- body.linkedin_url = linkedinUrl;
147
+ body.linkedinStandardProfileURL = linkedinUrl;
93
148
  }
94
- const response = await (0, http_retry_1.postWithRetry)(`${apiUrl}/job/find-email`, body, {
95
- Authorization: `Bearer ${apiKey}`,
149
+ const submitResponse = await (0, http_retry_1.postWithRetry)(`${apiUrl}/job/find_email`, body, {
150
+ "x-api-key": apiKey,
96
151
  }, {
97
152
  retries: 2,
98
153
  backoffMs: 500,
99
154
  timeoutMs,
100
155
  retryOnStatus: [429, 500, 502, 503, 504],
101
156
  });
102
- // Check if job completed successfully
103
- if (response.status !== "completed") {
157
+ if (!submitResponse.job_id) {
158
+ return null;
159
+ }
160
+ // Poll for job completion
161
+ const response = await pollJobResult(apiUrl, apiKey, submitResponse.job_id, timeoutMs);
162
+ if (!response) {
104
163
  return null;
105
164
  }
106
165
  // Check if email was found
@@ -121,8 +180,8 @@ function createTryKittProvider(config) {
121
180
  score: confidence,
122
181
  };
123
182
  }
124
- catch {
125
- // Silently fail - let other providers try
183
+ catch (err) {
184
+ console.error("[TryKitt] Email lookup failed:", err instanceof Error ? err.message : err);
126
185
  return null;
127
186
  }
128
187
  }
@@ -147,15 +206,19 @@ async function findEmailWithTryKitt(fullName, domain, config, linkedinUrl) {
147
206
  return null;
148
207
  }
149
208
  try {
209
+ // API expects camelCase parameters
210
+ // callbackType: "here" forces synchronous/realtime processing
150
211
  const body = {
151
- full_name: fullName,
152
- domain: domain,
212
+ fullName: fullName,
213
+ domainOrWebsite: domain,
214
+ callbackType: "here",
215
+ callbackURL: "",
153
216
  };
154
217
  if (linkedinUrl) {
155
- body.linkedin_url = linkedinUrl;
218
+ body.linkedinStandardProfileURL = linkedinUrl;
156
219
  }
157
- const response = await (0, http_retry_1.postWithRetry)(`${apiUrl}/job/find-email`, body, {
158
- Authorization: `Bearer ${apiKey}`,
220
+ const response = await (0, http_retry_1.postWithRetry)(`${apiUrl}/job/find_email`, body, {
221
+ "x-api-key": apiKey,
159
222
  }, {
160
223
  retries: 2,
161
224
  backoffMs: 500,
@@ -163,7 +226,8 @@ async function findEmailWithTryKitt(fullName, domain, config, linkedinUrl) {
163
226
  });
164
227
  return response;
165
228
  }
166
- catch {
229
+ catch (err) {
230
+ console.error("[TryKitt] findEmailWithTryKitt failed:", err instanceof Error ? err.message : err);
167
231
  return null;
168
232
  }
169
233
  }
@@ -185,8 +249,8 @@ async function verifyEmailWithTryKitt(email, config) {
185
249
  return null;
186
250
  }
187
251
  try {
188
- const response = await (0, http_retry_1.postWithRetry)(`${apiUrl}/job/verify-email`, { email }, {
189
- Authorization: `Bearer ${apiKey}`,
252
+ const response = await (0, http_retry_1.postWithRetry)(`${apiUrl}/job/verify_email`, { email }, {
253
+ "x-api-key": apiKey,
190
254
  }, {
191
255
  retries: 2,
192
256
  backoffMs: 500,
@@ -204,7 +268,8 @@ async function verifyEmailWithTryKitt(email, config) {
204
268
  isRoleAccount: response.result.is_role_account ?? false,
205
269
  };
206
270
  }
207
- catch {
271
+ catch (err) {
272
+ console.error("[TryKitt] Email verification failed:", err instanceof Error ? err.message : err);
208
273
  return null;
209
274
  }
210
275
  }
package/dist/index.d.ts CHANGED
@@ -48,7 +48,7 @@ export { initializeLinkedInClient, getConfig } from "./config";
48
48
  export type { LinkedInClientConfig } from "./config";
49
49
  export * from "./linkedin-api";
50
50
  export * from "./types";
51
- export type { LinkedInTenure, LinkedInPosition, LinkedInSpotlightBadge, SalesLeadSearchResult, } from "./types";
51
+ export type { LinkedInTenure, LinkedInPosition, LinkedInSpotlightBadge, SalesLeadSearchResult, LinkedInReactionType, ReactionActor, PostReaction, PostReactionsResult, CommentAuthor, PostComment, PostCommentsResult, ProfilePost, PostHistoryResult, PostAnalytics, AnalyzedPost, ProfileAnalysisOptions, ProfileAnalysisResult, } from "./types";
52
52
  export { LinkedInClientError } from "./utils/errors";
53
53
  export * from "./cosiall-client";
54
54
  export * from "./cookie-pool";
@@ -1,4 +1,4 @@
1
- import type { SalesSearchFilters, LinkedInProfile, SearchSalesResult, TypeaheadResult, SalesNavigatorProfile, SalesNavigatorProfileFull, Company } from "./types";
1
+ import type { SalesSearchFilters, LinkedInProfile, SearchSalesResult, TypeaheadResult, SalesNavigatorProfile, SalesNavigatorProfileFull, Company, PostReactionsResult, PostCommentsResult, PostHistoryResult, ProfileAnalysisOptions, ProfileAnalysisResult } from "./types";
2
2
  /**
3
3
  * Fetches a LinkedIn profile by vanity URL (public identifier).
4
4
  * Results are cached for the configured TTL (default: 15 minutes).
@@ -81,6 +81,44 @@ export declare function searchSalesLeads(keywords: string, options?: {
81
81
  rawQuery?: string;
82
82
  sessionId?: string;
83
83
  }): Promise<SearchSalesResult>;
84
+ /**
85
+ * Analyzes a LinkedIn profile's posts with engagement data (reactions and comments).
86
+ * This is a high-level convenience function that orchestrates multiple API calls:
87
+ * 1. Fetches the profile by vanity URL or URN
88
+ * 2. Retrieves the profile's post history
89
+ * 3. Fetches reactions and comments for each post
90
+ *
91
+ * @param profileIdentifier - LinkedIn vanity URL, full profile URL, or FSD profile URN
92
+ * @param options - Analysis options for controlling fetch behavior
93
+ * @returns Complete analysis result with profile, posts, engagement data, and summary statistics
94
+ *
95
+ * @example
96
+ * ```typescript
97
+ * // Analyze by vanity URL
98
+ * const analysis = await analyzeProfilePosts('john-doe');
99
+ * console.log(analysis.summary.totalReactions);
100
+ * console.log(analysis.summary.avgCommentsPerPost);
101
+ *
102
+ * // Analyze by full LinkedIn URL
103
+ * const analysis = await analyzeProfilePosts('https://www.linkedin.com/in/john-doe');
104
+ *
105
+ * // Analyze with options
106
+ * const analysis = await analyzeProfilePosts('john-doe', {
107
+ * postsCount: 20,
108
+ * reactionsPerPost: 50,
109
+ * commentsPerPost: 25,
110
+ * concurrency: 5,
111
+ * });
112
+ *
113
+ * // Access individual post engagement
114
+ * for (const analyzed of analysis.analyzedPosts) {
115
+ * console.log(`Post: ${analyzed.post.text?.substring(0, 50)}...`);
116
+ * console.log(` Reactions: ${analyzed.reactions.totalCount}`);
117
+ * console.log(` Comments: ${analyzed.comments.totalCount}`);
118
+ * }
119
+ * ```
120
+ */
121
+ export declare function analyzeProfilePosts(profileIdentifier: string, options?: ProfileAnalysisOptions): Promise<ProfileAnalysisResult>;
84
122
  export declare function getProfilesBatch(vanities: string[], concurrency?: number): Promise<(LinkedInProfile | null)[]>;
85
123
  export declare function resolveCompanyUniversalName(universalName: string): Promise<{
86
124
  companyId?: string;
@@ -167,3 +205,77 @@ export declare function getSalesNavigatorProfileFull(profileUrnOrId: string): Pr
167
205
  * ```
168
206
  */
169
207
  export declare function extractLinkedInHandle(flagshipProfileUrl: string | null | undefined): string | null;
208
+ /**
209
+ * Fetches reactions (likes, celebrates, etc.) for a LinkedIn post.
210
+ * Uses the Voyager GraphQL API with pagination support.
211
+ *
212
+ * @param activityUrn - The activity URN (e.g., "urn:li:activity:7419061111293083648") or bare ID
213
+ * @param options - Pagination options
214
+ * @param options.start - Pagination offset (default: 0)
215
+ * @param options.count - Number of reactions to fetch (default: 10, max: 100)
216
+ * @returns PostReactionsResult with reactions array and pagination info
217
+ * @throws LinkedInClientError with code NOT_FOUND if post doesn't exist
218
+ *
219
+ * @example
220
+ * ```typescript
221
+ * // Get first 10 reactions
222
+ * const reactions = await getPostReactions('urn:li:activity:7419061111293083648');
223
+ * console.log(reactions.totalCount); // 37
224
+ * console.log(reactions.reactions[0].reactionType); // "LIKE"
225
+ *
226
+ * // Paginate through all reactions
227
+ * const page2 = await getPostReactions('7419061111293083648', { start: 10, count: 10 });
228
+ * ```
229
+ */
230
+ export declare function getPostReactions(activityUrn: string, options?: {
231
+ start?: number;
232
+ count?: number;
233
+ }): Promise<PostReactionsResult>;
234
+ /**
235
+ * Fetches comments for a LinkedIn post.
236
+ * Uses the voyagerFeedDashUpdates GraphQL API.
237
+ *
238
+ * @param activityUrn - The activity URN (e.g., "urn:li:activity:7419061111293083648") or bare ID
239
+ * @param options - Fetch options
240
+ * @param options.commentsCount - Number of comments to fetch (default: 10, max: 100)
241
+ * @returns PostCommentsResult with comments array
242
+ * @throws LinkedInClientError with code NOT_FOUND if post doesn't exist
243
+ *
244
+ * @example
245
+ * ```typescript
246
+ * // Get comments for a post
247
+ * const comments = await getPostComments('urn:li:activity:7419061111293083648');
248
+ * console.log(comments.totalCount);
249
+ *
250
+ * // Get more comments
251
+ * const moreComments = await getPostComments('7419061111293083648', { commentsCount: 50 });
252
+ * ```
253
+ */
254
+ export declare function getPostComments(activityUrn: string, options?: {
255
+ commentsCount?: number;
256
+ }): Promise<PostCommentsResult>;
257
+ /**
258
+ * Fetches a user's post history (their published posts).
259
+ * Uses the Voyager profileUpdatesV2 API with pagination support.
260
+ *
261
+ * @param profileUrn - The FSD profile URN (e.g., "urn:li:fsd_profile:ABC123xyz") or bare key
262
+ * @param options - Pagination options
263
+ * @param options.start - Pagination offset (default: 0)
264
+ * @param options.count - Number of posts to fetch (default: 10, max: 100)
265
+ * @returns PostHistoryResult with posts array and pagination info
266
+ * @throws LinkedInClientError with code NOT_FOUND if profile doesn't exist
267
+ *
268
+ * @example
269
+ * ```typescript
270
+ * // Get first 10 posts
271
+ * const posts = await getPostHistory('ABC123xyz');
272
+ * console.log(posts.posts.length);
273
+ *
274
+ * // Paginate
275
+ * const page2 = await getPostHistory('ABC123xyz', { start: 10, count: 10 });
276
+ * ```
277
+ */
278
+ export declare function getPostHistory(profileUrn: string, options?: {
279
+ start?: number;
280
+ count?: number;
281
+ }): Promise<PostHistoryResult>;