linkedin-secret-sauce 0.12.2 → 0.12.3

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 (145) hide show
  1. package/README.md +292 -13
  2. package/dist/enrichment/index.d.ts +22 -1
  3. package/dist/enrichment/index.js +27 -20
  4. package/dist/enrichment/types.d.ts +10 -0
  5. package/docs/COSIALL_PROFILE_EMAILS.md +342 -0
  6. package/docs/ENRICHMENT.md +622 -0
  7. package/docs/INTEGRATION.md +405 -0
  8. package/docs/PLAYGROUND.md +558 -0
  9. package/docs/SALES_SEARCH.md +171 -0
  10. package/docs/api/.nojekyll +1 -0
  11. package/docs/api/assets/hierarchy.js +1 -0
  12. package/docs/api/assets/highlight.css +92 -0
  13. package/docs/api/assets/icons.js +18 -0
  14. package/docs/api/assets/icons.svg +1 -0
  15. package/docs/api/assets/main.js +60 -0
  16. package/docs/api/assets/navigation.js +1 -0
  17. package/docs/api/assets/search.js +1 -0
  18. package/docs/api/assets/style.css +1633 -0
  19. package/docs/api/classes/LinkedInClientError.html +37 -0
  20. package/docs/api/functions/_testGetAccountCookies.html +4 -0
  21. package/docs/api/functions/_testGetAccountEntry.html +4 -0
  22. package/docs/api/functions/_testGetAllAccountIds.html +3 -0
  23. package/docs/api/functions/_testGetPoolState.html +3 -0
  24. package/docs/api/functions/adminResetAccount.html +1 -0
  25. package/docs/api/functions/adminSetCooldown.html +1 -0
  26. package/docs/api/functions/buildCookieHeader.html +1 -0
  27. package/docs/api/functions/clearAllSmartLeadTokens.html +2 -0
  28. package/docs/api/functions/clearRequestHistory.html +1 -0
  29. package/docs/api/functions/clearSessionAccount.html +1 -0
  30. package/docs/api/functions/clearSmartLeadToken.html +2 -0
  31. package/docs/api/functions/createEnrichmentClient.html +8 -0
  32. package/docs/api/functions/extractCsrfToken.html +1 -0
  33. package/docs/api/functions/extractLinkedInHandle.html +7 -0
  34. package/docs/api/functions/fetchCookiesFromCosiall.html +14 -0
  35. package/docs/api/functions/fetchProfileEmailsFromCosiall.html +18 -0
  36. package/docs/api/functions/forceRefreshCookies.html +1 -0
  37. package/docs/api/functions/getAccountForSession.html +1 -0
  38. package/docs/api/functions/getAccountsSummary.html +1 -0
  39. package/docs/api/functions/getCompaniesBatch.html +5 -0
  40. package/docs/api/functions/getCompanyById.html +9 -0
  41. package/docs/api/functions/getCompanyByUrl.html +1 -0
  42. package/docs/api/functions/getConfig.html +1 -0
  43. package/docs/api/functions/getCookiePoolHealth.html +1 -0
  44. package/docs/api/functions/getProfileByUrn.html +17 -0
  45. package/docs/api/functions/getProfileByVanity.html +10 -0
  46. package/docs/api/functions/getProfilesBatch.html +1 -0
  47. package/docs/api/functions/getRequestHistory.html +1 -0
  48. package/docs/api/functions/getSalesNavigatorProfileDetails.html +1 -0
  49. package/docs/api/functions/getSalesNavigatorProfileFull.html +16 -0
  50. package/docs/api/functions/getSmartLeadToken.html +1 -0
  51. package/docs/api/functions/getSmartLeadTokenCacheStats.html +2 -0
  52. package/docs/api/functions/getSmartLeadUser.html +2 -0
  53. package/docs/api/functions/getSnapshot.html +1 -0
  54. package/docs/api/functions/getYearsAtCompanyOptions.html +2 -0
  55. package/docs/api/functions/getYearsInPositionOptions.html +2 -0
  56. package/docs/api/functions/getYearsOfExperienceOptions.html +2 -0
  57. package/docs/api/functions/incrementMetric.html +1 -0
  58. package/docs/api/functions/initializeCookiePool.html +1 -0
  59. package/docs/api/functions/initializeLinkedInClient.html +1 -0
  60. package/docs/api/functions/isBusinessEmail.html +4 -0
  61. package/docs/api/functions/isDisposableDomain.html +4 -0
  62. package/docs/api/functions/isDisposableEmail.html +4 -0
  63. package/docs/api/functions/isPersonalDomain.html +4 -0
  64. package/docs/api/functions/isPersonalEmail.html +4 -0
  65. package/docs/api/functions/isRoleAccount.html +4 -0
  66. package/docs/api/functions/isValidEmailSyntax.html +4 -0
  67. package/docs/api/functions/parseFullProfile.html +15 -0
  68. package/docs/api/functions/parseSalesSearchResults.html +1 -0
  69. package/docs/api/functions/reportAccountFailure.html +1 -0
  70. package/docs/api/functions/reportAccountSuccess.html +1 -0
  71. package/docs/api/functions/resolveCompanyUniversalName.html +1 -0
  72. package/docs/api/functions/searchSalesLeads.html +16 -0
  73. package/docs/api/functions/selectAccountForRequest.html +1 -0
  74. package/docs/api/functions/setAccountForSession.html +1 -0
  75. package/docs/api/functions/typeahead.html +1 -0
  76. package/docs/api/functions/verifyEmailMx.html +1 -0
  77. package/docs/api/hierarchy.html +1 -0
  78. package/docs/api/index.html +12 -0
  79. package/docs/api/interfaces/AccountCookies.html +4 -0
  80. package/docs/api/interfaces/BatchEnrichmentOptions.html +14 -0
  81. package/docs/api/interfaces/CacheAdapter.html +6 -0
  82. package/docs/api/interfaces/CanonicalEmail.html +14 -0
  83. package/docs/api/interfaces/Company.html +17 -0
  84. package/docs/api/interfaces/ConstructConfig.html +8 -0
  85. package/docs/api/interfaces/CosiallProfileEmailsResponse.html +11 -0
  86. package/docs/api/interfaces/DropcontactConfig.html +3 -0
  87. package/docs/api/interfaces/EnrichmentCandidate.html +34 -0
  88. package/docs/api/interfaces/EnrichmentClient.html +10 -0
  89. package/docs/api/interfaces/EnrichmentClientConfig.html +12 -0
  90. package/docs/api/interfaces/EnrichmentLogger.html +6 -0
  91. package/docs/api/interfaces/EnrichmentOptions.html +10 -0
  92. package/docs/api/interfaces/HunterConfig.html +3 -0
  93. package/docs/api/interfaces/LddConfig.html +4 -0
  94. package/docs/api/interfaces/LddProfileData.html +6 -0
  95. package/docs/api/interfaces/LinkedInClientConfig.html +20 -0
  96. package/docs/api/interfaces/LinkedInCookie.html +9 -0
  97. package/docs/api/interfaces/LinkedInPosition.html +14 -0
  98. package/docs/api/interfaces/LinkedInProfile.html +21 -0
  99. package/docs/api/interfaces/LinkedInSpotlightBadge.html +5 -0
  100. package/docs/api/interfaces/LinkedInTenure.html +3 -0
  101. package/docs/api/interfaces/Metrics.html +22 -0
  102. package/docs/api/interfaces/MetricsSnapshot.html +23 -0
  103. package/docs/api/interfaces/ProfileEducation.html +8 -0
  104. package/docs/api/interfaces/ProfileEmailsLookupOptions.html +9 -0
  105. package/docs/api/interfaces/ProfilePosition.html +12 -0
  106. package/docs/api/interfaces/ProfileSkill.html +3 -0
  107. package/docs/api/interfaces/ProviderResult.html +11 -0
  108. package/docs/api/interfaces/ProvidersConfig.html +17 -0
  109. package/docs/api/interfaces/RequestHistoryEntry.html +8 -0
  110. package/docs/api/interfaces/SalesLeadSearchResult.html +31 -0
  111. package/docs/api/interfaces/SalesNavigatorContactInfo.html +5 -0
  112. package/docs/api/interfaces/SalesNavigatorPosition.html +11 -0
  113. package/docs/api/interfaces/SalesNavigatorProfile.html +9 -0
  114. package/docs/api/interfaces/SalesNavigatorProfileFull.html +24 -0
  115. package/docs/api/interfaces/SearchSalesResult.html +5 -0
  116. package/docs/api/interfaces/SmartLeadAuthConfig.html +6 -0
  117. package/docs/api/interfaces/SmartLeadCredentials.html +3 -0
  118. package/docs/api/interfaces/SmartLeadLoginResponse.html +3 -0
  119. package/docs/api/interfaces/SmartLeadUser.html +8 -0
  120. package/docs/api/interfaces/SmartProspectConfig.html +19 -0
  121. package/docs/api/interfaces/SmartProspectContact.html +24 -0
  122. package/docs/api/interfaces/SmartProspectSearchFilters.html +42 -0
  123. package/docs/api/interfaces/TypeaheadItem.html +3 -0
  124. package/docs/api/interfaces/TypeaheadResult.html +3 -0
  125. package/docs/api/interfaces/VerificationResult.html +16 -0
  126. package/docs/api/types/CostCallback.html +2 -0
  127. package/docs/api/types/Geo.html +4 -0
  128. package/docs/api/types/LddApiResponse.html +1 -0
  129. package/docs/api/types/ProviderFunc.html +2 -0
  130. package/docs/api/types/ProviderName.html +2 -0
  131. package/docs/api/types/SalesSearchFilters.html +7 -0
  132. package/docs/api/types/TypeaheadType.html +1 -0
  133. package/docs/api/variables/COMPANY_SIZE_OPTIONS.html +1 -0
  134. package/docs/api/variables/DEFAULT_PROVIDER_ORDER.html +20 -0
  135. package/docs/api/variables/DISPOSABLE_DOMAINS.html +2 -0
  136. package/docs/api/variables/FUNCTION_OPTIONS.html +1 -0
  137. package/docs/api/variables/INDUSTRY_OPTIONS.html +1 -0
  138. package/docs/api/variables/LANGUAGE_OPTIONS.html +1 -0
  139. package/docs/api/variables/PERSONAL_DOMAINS.html +2 -0
  140. package/docs/api/variables/PROVIDER_COSTS.html +15 -0
  141. package/docs/api/variables/REGION_OPTIONS.html +1 -0
  142. package/docs/api/variables/SENIORITY_OPTIONS.html +3 -0
  143. package/docs/api/variables/YEARS_OPTIONS.html +1 -0
  144. package/docs/index.html +98 -0
  145. package/package.json +40 -28
@@ -0,0 +1,405 @@
1
+ # LinkedIn Secret Sauce — Integration Guide
2
+
3
+ Use this doc as a drop-in "How to use" for other apps that want to call LinkedIn Sales Navigator and enrich emails via this library.
4
+
5
+ - Runtime: Node.js 18+ (uses global `fetch`)
6
+ - Package: `linkedin-secret-sauce`
7
+ - Scope: server-side use only
8
+
9
+ ## Install
10
+
11
+ - pnpm: `pnpm add linkedin-secret-sauce@^0.12.0`
12
+ - npm: `npm i linkedin-secret-sauce@^0.12.0`
13
+ - GitHub (fallback/private): `pnpm add github:enerage/LinkedInSecretSauce#v0.12.2`
14
+
15
+ Versioning
16
+ - Recommend a semver range in consumers, e.g. `^0.12.0`.
17
+ - In monorepos, add a root override to force-sync if needed:
18
+ - package.json (root): `{ "overrides": { "linkedin-secret-sauce": "0.12.2" } }`
19
+ - Update across workspace: `pnpm -r up linkedin-secret-sauce`
20
+
21
+ ## Environment
22
+
23
+ Add to your app's `.env` or secrets:
24
+
25
+ ```
26
+ COSIALL_API_URL=https://api.cosiall.com # Your Cosiall base URL
27
+ COSIALL_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx # Your Cosiall API key
28
+ LINKEDIN_PROXY_STRING=proxy.host:8080:user:pass # Optional; supports URL forms too
29
+ LOG_LEVEL=info # debug|info|warn|error
30
+ ```
31
+
32
+ ## Initialize Once
33
+
34
+ ```ts
35
+ // bootstrap/linkedin.ts
36
+ import { initializeLinkedInClient, type LinkedInClientConfig } from 'linkedin-secret-sauce';
37
+
38
+ export async function initLinkedIn() {
39
+ const cfg: LinkedInClientConfig = {
40
+ cosiallApiUrl: process.env.COSIALL_API_URL!,
41
+ cosiallApiKey: process.env.COSIALL_API_KEY!,
42
+ proxyString: process.env.LINKEDIN_PROXY_STRING, // optional
43
+ logLevel: (process.env.LOG_LEVEL as any) || 'info',
44
+
45
+ // Optional overrides (defaults shown)
46
+ profileCacheTtl: 15 * 60 * 1000,
47
+ searchCacheTtl: 15 * 60 * 1000,
48
+ companyCacheTtl: 10 * 60 * 1000,
49
+ typeaheadCacheTtl: 60 * 60 * 1000,
50
+ cookieRefreshInterval: 15 * 60 * 1000,
51
+ cookieFreshnessWindow: 24 * 60 * 60 * 1000,
52
+ maxRetries: 2,
53
+ retryDelayMs: 700,
54
+ accountCooldownMs: 5000,
55
+ maxFailuresBeforeCooldown: 3,
56
+ maxRequestHistory: 500,
57
+
58
+ // NEW: Advanced initialization options
59
+ eagerInitialization: true, // Default: true - Fetch cookies during init (recommended)
60
+ fallbackWithoutProxyOnError: false, // Default: false - Retry without proxy on 502/503/504
61
+
62
+ // Optional error handler for initialization failures
63
+ onInitializationError: (error) => {
64
+ console.error('LinkedIn init failed:', error);
65
+ // Handle gracefully instead of throwing
66
+ },
67
+
68
+ // Optional Sentry integration for error tracking
69
+ sentryClient: mySentryClient, // Pass your Sentry client instance
70
+ };
71
+
72
+ await initializeLinkedInClient(cfg);
73
+ }
74
+ ```
75
+
76
+ Call `await initLinkedIn()` once at process startup (before making any API calls).
77
+
78
+ ### Configuration Reference
79
+
80
+ | Option | Type | Default | Description |
81
+ |--------|------|---------|-------------|
82
+ | `cosiallApiUrl` | `string` | **required** | Your Cosiall backend URL |
83
+ | `cosiallApiKey` | `string` | **required** | Your Cosiall API key |
84
+ | `proxyString` | `string` | `undefined` | Optional proxy (URL or colon format) |
85
+ | `logLevel` | `string` | `'info'` | Log verbosity: `debug`, `info`, `warn`, `error` |
86
+ | `profileCacheTtl` | `number` | `900000` | Profile cache TTL (15 minutes) |
87
+ | `searchCacheTtl` | `number` | `900000` | Search cache TTL (15 minutes) |
88
+ | `companyCacheTtl` | `number` | `600000` | Company cache TTL (10 minutes) |
89
+ | `typeaheadCacheTtl` | `number` | `3600000` | Typeahead cache TTL (1 hour) |
90
+ | `cookieRefreshInterval` | `number` | `900000` | How often to refresh cookie pool (15 min) |
91
+ | `cookieFreshnessWindow` | `number` | `86400000` | Max age for cookies (24 hours) |
92
+ | `maxRetries` | `number` | `2` | Retries on 5xx errors (same account) |
93
+ | `retryDelayMs` | `number` | `700` | Delay between retries |
94
+ | `accountCooldownMs` | `number` | `5000` | Cooldown after account rotation |
95
+ | `maxFailuresBeforeCooldown` | `number` | `3` | Failures before triggering cooldown |
96
+ | `maxRequestHistory` | `number` | `500` | Max requests in history log |
97
+ | `eagerInitialization` | `boolean` | `true` | Fetch cookies during init (recommended) |
98
+ | `fallbackWithoutProxyOnError` | `boolean` | `false` | Retry without proxy on 502/503/504 |
99
+ | `onInitializationError` | `function` | `undefined` | Optional init error handler |
100
+ | `sentryClient` | `SentryClient` | `undefined` | Optional Sentry integration |
101
+
102
+ ## Common Calls
103
+
104
+ ```ts
105
+ import {
106
+ // Profiles
107
+ getProfileByVanity, getProfileByUrn, getProfilesBatch,
108
+ // Sales search + typeahead
109
+ searchSalesLeads, typeahead,
110
+ // Companies
111
+ resolveCompanyUniversalName, getCompanyById, getCompanyByUrl,
112
+ // Sales profile
113
+ getSalesNavigatorProfileDetails,
114
+ // Types (optional)
115
+ type LinkedInProfile, type SearchSalesResult, type Company,
116
+ type TypeaheadResult, type SalesNavigatorProfile,
117
+ } from 'linkedin-secret-sauce';
118
+
119
+ // Profiles
120
+ const prof1: LinkedInProfile = await getProfileByVanity('satyanadella');
121
+ const prof2: LinkedInProfile = await getProfileByUrn('urn:li:fsd_profile:ACwAAAbCdEf'); // URN or bare key
122
+ const many = await getProfilesBatch(['a', 'b', 'c'], 4); // array aligned to inputs, nulls on failures
123
+
124
+ // Sales search (returns { items, page } when options passed)
125
+ const leads: SearchSalesResult = await searchSalesLeads('cto fintech london', { start: 0, count: 25 });
126
+ // Note: on HTTP 400 with decoration "-14", the client automatically retries with "-17".
127
+
128
+ // Typeahead
129
+ const geo: TypeaheadResult = await typeahead({ type: 'BING_GEO', query: 'new', start: 0, count: 10 });
130
+
131
+ // Static filters (years) - no API call needed, instant response
132
+ import {
133
+ getYearsAtCompanyOptions,
134
+ getYearsInPositionOptions,
135
+ getYearsOfExperienceOptions,
136
+ YEARS_AT_COMPANY_OPTIONS,
137
+ YEARS_IN_POSITION_OPTIONS,
138
+ YEARS_OF_EXPERIENCE_OPTIONS,
139
+ } from 'linkedin-secret-sauce';
140
+
141
+ const yearsAtCompany: TypeaheadResult = await getYearsAtCompanyOptions();
142
+ const yearsInPosition: TypeaheadResult = await getYearsInPositionOptions();
143
+ const yearsOfExperience: TypeaheadResult = await getYearsOfExperienceOptions();
144
+ // Returns: { items: [{ id: 1, text: "Less than 1 year", displayValue: "< 1 year" }, ...], page: { start: 0, count: 5, total: 5 } }
145
+
146
+ // Or access constants directly (sync)
147
+ const options = YEARS_AT_COMPANY_OPTIONS; // readonly array of { id, text, displayValue }
148
+
149
+ // Companies
150
+ const { companyId } = await resolveCompanyUniversalName('microsoft');
151
+ const company: Company = await getCompanyById(companyId!);
152
+ const same: Company = await getCompanyByUrl('https://www.linkedin.com/company/microsoft/');
153
+
154
+ // Sales Navigator profile details
155
+ const sales1: SalesNavigatorProfile =
156
+ await getSalesNavigatorProfileDetails('urn:li:fs_salesProfile:ACwAAA...');
157
+ const sales2: SalesNavigatorProfile =
158
+ await getSalesNavigatorProfileDetails('profileId:ACwAAA...,authType:OUT_OF_NETWORK,authToken:xyz');
159
+ // If the triple form returns 400 in your environment, the client automatically retries with the bare id.
160
+ ```
161
+
162
+ ## Email Enrichment
163
+
164
+ The library includes a powerful email enrichment module that finds and verifies business emails using multiple providers in an optimized 3-phase strategy.
165
+
166
+ ```ts
167
+ import { createEnrichmentClient } from 'linkedin-secret-sauce';
168
+
169
+ const enrichment = createEnrichmentClient({
170
+ providers: {
171
+ // FREE providers (Phase 1 - parallel)
172
+ ldd: { apiUrl: process.env.LDD_API_URL, apiToken: process.env.LDD_API_TOKEN },
173
+ smartprospect: { email: process.env.SMARTLEAD_EMAIL, password: process.env.SMARTLEAD_PASSWORD },
174
+ trykitt: { apiKey: process.env.TRYKITT_API_KEY },
175
+ // construct and cosiall are enabled by default
176
+
177
+ // Verification (Phase 2)
178
+ bounceban: { apiKey: process.env.BOUNCEBAN_API_KEY },
179
+
180
+ // Paid finders (Phase 3 - only if needed)
181
+ hunter: { apiKey: process.env.HUNTER_API_KEY },
182
+ snovio: { clientId: process.env.SNOVIO_CLIENT_ID, clientSecret: process.env.SNOVIO_CLIENT_SECRET },
183
+ },
184
+ });
185
+
186
+ const result = await enrichment.enrich({
187
+ firstName: 'John',
188
+ lastName: 'Doe',
189
+ company: 'Acme Corp',
190
+ domain: 'acme.com',
191
+ linkedinUrl: 'https://linkedin.com/in/johndoe',
192
+ });
193
+
194
+ console.log(result.business_email); // john.doe@acme.com
195
+ console.log(result.business_email_source); // 'trykitt'
196
+ console.log(result.business_email_verified); // true
197
+ console.log(result.business_email_confidence); // 95
198
+ ```
199
+
200
+ ### Supported Enrichment Providers
201
+
202
+ **FREE Providers:**
203
+ - `construct` - Pattern guessing + MX verification (always enabled)
204
+ - `cosiall` - Emails from LinkedIn profiles (uses global config)
205
+ - `ldd` - LinkedIn Data Dump database (~500M verified emails)
206
+ - `smartprospect` - SmartLead prospect database
207
+ - `trykitt` - AI-powered email finder with catch-all verification
208
+
209
+ **PAID Providers:**
210
+ - `bounceban` - FREE single / $0.003 bulk - Catch-all specialist
211
+ - `hunter` - $0.005/email - Domain search + email finder
212
+ - `bouncer` - $0.006/email - SMTP verification (99%+ accuracy)
213
+ - `dropcontact` - $0.01/email - Email finder API
214
+ - `snovio` - $0.02/email - Email finding + verification
215
+
216
+ For complete enrichment documentation, provider configuration, cost tracking, and advanced features, see **[ENRICHMENT.md](./ENRICHMENT.md)**.
217
+
218
+ ## Advanced Lead Search (filters)
219
+
220
+ See `docs/SALES_SEARCH.md` for a full, strongly-typed `SalesSearchFilters` schema and examples.
221
+
222
+ Quick preview:
223
+
224
+ ```ts
225
+ const out = await searchSalesLeads('cto fintech', {
226
+ start: 0,
227
+ count: 25,
228
+ filters: {
229
+ // Titles allow id and free text
230
+ role: {
231
+ current_titles: { include: [{ id: 9 }, { text: 'Head of RevOps' }] },
232
+ seniority_ids: [320, 310],
233
+ functions: { include: ['ENGINEERING','IT'] },
234
+ },
235
+ personal: {
236
+ geography: { include: [{ type: 'country', value: 'United States', id: '103644278' }] },
237
+ profile_language: { include: ['en'] },
238
+ years_at_company_ids: [2,3], // bucket ids 1..5
239
+ years_in_current_role_ids: [1,2,3],
240
+ years_experience_ids: [4,5],
241
+ },
242
+ company: {
243
+ current: { include: ['urn:li:organization:96151950'] }, // current company only
244
+ headcount: { include: ['51-200','201-500'] }, // or ['D','E']
245
+ type: { include: ['PUBLIC','PRIVATE'] }, // or ['C','P']
246
+ },
247
+ spotlights: { posted_in_last_days: 30 },
248
+ },
249
+ });
250
+ ```
251
+
252
+ Notes
253
+ - Relationship filters are not used in this package (customer-agnostic results).
254
+ - Past company is intentionally not emitted — only CURRENT_COMPANY is supported.
255
+ - Large facets (locations, industries, titles, functions): hydrate ids via typeahead.
256
+ - Example: `type: 'COMPANY_WITH_LIST'` to fetch company org URNs, then pass `urn:li:organization:ID`.
257
+ - `type: 'BING_GEO'` for person geography (REGION facet ids).
258
+ - **Years filters** (years_at_company_ids, years_in_current_role_ids, years_experience_ids):
259
+ - Use the static helper functions `getYearsAtCompanyOptions()`, `getYearsInPositionOptions()`, `getYearsOfExperienceOptions()`
260
+ - These return predefined ranges (< 1 year, 1-2 years, 3-5 years, 6-10 years, 10+ years) with bucket ids 1-5
261
+ - **No API call needed** - instant response from in-memory constants
262
+ - Example: `years_at_company_ids: [2,3]` filters for "1-2 years" and "3-5 years" tenure
263
+
264
+ Power users:
265
+ - You can pass a complete `rawQuery` string to `searchSalesLeads` if you already have the full `query=(...)` payload; it takes precedence over `filters`.
266
+
267
+ ## Error Handling
268
+
269
+ All throws are `LinkedInClientError` with fields `code`, `status?`, and `accountId?`.
270
+
271
+ ```ts
272
+ import { LinkedInClientError } from 'linkedin-secret-sauce';
273
+
274
+ try {
275
+ const res = await searchSalesLeads('cto');
276
+ // ...
277
+ } catch (e) {
278
+ const err = e as LinkedInClientError;
279
+ switch (err.code) {
280
+ case 'NOT_FOUND':
281
+ case 'INVALID_INPUT':
282
+ case 'REQUEST_FAILED':
283
+ case 'ALL_ACCOUNTS_FAILED':
284
+ case 'NOT_INITIALIZED':
285
+ case 'INVALID_CONFIG':
286
+ case 'INITIALIZATION_FAILED':
287
+ // handle/log; check err.status and err.accountId
288
+ break;
289
+ }
290
+ }
291
+ ```
292
+
293
+ ## Metrics and Request History
294
+
295
+ ```ts
296
+ import { getSnapshot, getRequestHistory, clearRequestHistory } from 'linkedin-secret-sauce';
297
+
298
+ // Metrics (counters for requests, retries, cache hits, etc.)
299
+ const snap = getSnapshot(); // { profileFetches, httpSuccess, httpFailures, ... }
300
+
301
+ // Request history (bounded in-memory log; secrets redacted)
302
+ const recent = getRequestHistory();
303
+ clearRequestHistory();
304
+ ```
305
+
306
+ ## Account Pool & Proxy
307
+
308
+ - Accounts (cookies) are fetched from your Cosiall backend (`/api/flexiq/linkedin-cookies/all`, authorized with `COSIALL_API_KEY`).
309
+ - The pool round-robins across accounts, rotates on 401/403/429 (auth/rate limit) and applies cooldowns, and periodically refreshes.
310
+ - With `eagerInitialization: true` (default), cookies are fetched during `initializeLinkedInClient()` for faster first requests.
311
+ - Proxy: pass `proxyString` in config.
312
+ - Accepted forms:
313
+ - URL: `http://user:pass@host:port` (IPv6 `[::1]` supported)
314
+ - Colon: `host:port[:user:pass]`
315
+ - The client sets `HTTP_PROXY`/`HTTPS_PROXY` during the request and restores afterwards.
316
+ - If `fallbackWithoutProxyOnError: true`, the client retries without proxy on 502/503/504 errors.
317
+
318
+ ## Minimal Express Route Example
319
+
320
+ ```ts
321
+ import express from 'express';
322
+ import { initLinkedIn } from './bootstrap/linkedin'; // your wrapper above
323
+ import {
324
+ getProfileByVanity, searchSalesLeads, getCompanyById,
325
+ typeahead, getSalesNavigatorProfileDetails,
326
+ createEnrichmentClient,
327
+ } from 'linkedin-secret-sauce';
328
+
329
+ await initLinkedIn();
330
+
331
+ // Optional: Set up email enrichment
332
+ const enrichment = createEnrichmentClient({
333
+ providers: {
334
+ trykitt: { apiKey: process.env.TRYKITT_API_KEY },
335
+ bounceban: { apiKey: process.env.BOUNCEBAN_API_KEY },
336
+ hunter: { apiKey: process.env.HUNTER_API_KEY },
337
+ },
338
+ });
339
+
340
+ const app = express();
341
+ app.use(express.json());
342
+
343
+ app.post('/api/profile/vanity', async (req, res) => {
344
+ const { vanity } = req.body;
345
+ res.json(await getProfileByVanity(vanity));
346
+ });
347
+
348
+ app.post('/api/search', async (req, res) => {
349
+ const { keywords, start = 0, count = 25 } = req.body;
350
+ res.json(await searchSalesLeads(keywords, { start, count }));
351
+ });
352
+
353
+ app.post('/api/company/by-id', async (req, res) => {
354
+ const { id } = req.body;
355
+ res.json(await getCompanyById(String(id)));
356
+ });
357
+
358
+ app.post('/api/typeahead', async (req, res) => {
359
+ const { type, query, start = 0, count = 10 } = req.body;
360
+ res.json(await typeahead({ type, query, start, count }));
361
+ });
362
+
363
+ app.post('/api/sales/profile', async (req, res) => {
364
+ const { idOrUrn } = req.body; // supports bare id, URN, or triple
365
+ res.json(await getSalesNavigatorProfileDetails(idOrUrn));
366
+ });
367
+
368
+ app.post('/api/enrich-email', async (req, res) => {
369
+ const { firstName, lastName, domain, company } = req.body;
370
+ const result = await enrichment.enrich({ firstName, lastName, domain, company });
371
+ res.json(result);
372
+ });
373
+
374
+ app.listen(3000, () => console.log('API listening on :3000'));
375
+ ```
376
+
377
+ ## Behavior Notes
378
+
379
+ - Caches: in-process memory with TTLs; stale entries evicted on access.
380
+ - Retries: 5xx retried on same account (`maxRetries`) with `retryDelayMs` backoff.
381
+ - Rotations: 401/403/429 trigger account rotation + cooldown.
382
+ - Logging: JSON lines to stdout with secrets masked (`cookie`, `authorization`, etc.).
383
+ - Initialization: With `eagerInitialization: true` (default), cookies are fetched during init for faster first requests.
384
+
385
+ ## Troubleshooting
386
+
387
+ - 400 on Sales Profile:
388
+ - The library auto-retries triple → bare id when 400 occurs.
389
+ - You can pass either a bare profile id/URN or the triple `(profileId:...,authType:...,authToken:...)`.
390
+ - Timeouts:
391
+ - Node 18's `fetch` has no default timeout; if you need a hard timeout, wrap calls with `AbortController` at the app level (or open an issue to add a built-in timeout).
392
+ - Initialization Failures:
393
+ - If `eagerInitialization: true` and cookie fetch fails, the client throws by default.
394
+ - Use `onInitializationError` callback to handle gracefully instead of throwing.
395
+ - Or set `eagerInitialization: false` to defer cookie fetching until first API call.
396
+ - Proxy Errors (502/503/504):
397
+ - Set `fallbackWithoutProxyOnError: true` to automatically retry without proxy on gateway errors.
398
+
399
+ ## CommonJS
400
+
401
+ If you're using CJS, use `require`:
402
+
403
+ ```js
404
+ const { initializeLinkedInClient, getProfileByVanity, createEnrichmentClient } = require('linkedin-secret-sauce');
405
+ ```