linkedin-secret-sauce 0.12.1 → 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.
- package/README.md +339 -31
- package/dist/cosiall-client.d.ts +1 -1
- package/dist/cosiall-client.js +1 -1
- package/dist/enrichment/index.d.ts +23 -2
- package/dist/enrichment/index.js +38 -22
- package/dist/enrichment/matching.d.ts +16 -2
- package/dist/enrichment/matching.js +387 -65
- package/dist/enrichment/providers/bounceban.d.ts +82 -0
- package/dist/enrichment/providers/bounceban.js +447 -0
- package/dist/enrichment/providers/bouncer.d.ts +1 -1
- package/dist/enrichment/providers/bouncer.js +19 -21
- package/dist/enrichment/providers/construct.d.ts +1 -1
- package/dist/enrichment/providers/construct.js +22 -38
- package/dist/enrichment/providers/cosiall.d.ts +1 -1
- package/dist/enrichment/providers/cosiall.js +3 -4
- package/dist/enrichment/providers/dropcontact.d.ts +15 -9
- package/dist/enrichment/providers/dropcontact.js +188 -19
- package/dist/enrichment/providers/hunter.d.ts +8 -1
- package/dist/enrichment/providers/hunter.js +52 -28
- package/dist/enrichment/providers/index.d.ts +2 -0
- package/dist/enrichment/providers/index.js +10 -1
- package/dist/enrichment/providers/ldd.d.ts +1 -10
- package/dist/enrichment/providers/ldd.js +20 -97
- package/dist/enrichment/providers/smartprospect.js +28 -48
- package/dist/enrichment/providers/snovio.d.ts +1 -1
- package/dist/enrichment/providers/snovio.js +29 -31
- package/dist/enrichment/providers/trykitt.d.ts +63 -0
- package/dist/enrichment/providers/trykitt.js +210 -0
- package/dist/enrichment/types.d.ts +220 -7
- package/dist/enrichment/types.js +16 -8
- package/dist/enrichment/utils/candidate-parser.d.ts +107 -0
- package/dist/enrichment/utils/candidate-parser.js +173 -0
- package/dist/enrichment/utils/noop-provider.d.ts +39 -0
- package/dist/enrichment/utils/noop-provider.js +37 -0
- package/dist/enrichment/utils/rate-limiter.d.ts +103 -0
- package/dist/enrichment/utils/rate-limiter.js +204 -0
- package/dist/enrichment/utils/validation.d.ts +75 -3
- package/dist/enrichment/utils/validation.js +164 -11
- package/dist/linkedin-api.d.ts +40 -1
- package/dist/linkedin-api.js +160 -27
- package/dist/types.d.ts +50 -1
- package/dist/utils/lru-cache.d.ts +105 -0
- package/dist/utils/lru-cache.js +175 -0
- package/docs/COSIALL_PROFILE_EMAILS.md +342 -0
- package/docs/ENRICHMENT.md +622 -0
- package/docs/INTEGRATION.md +405 -0
- package/docs/PLAYGROUND.md +558 -0
- package/docs/SALES_SEARCH.md +171 -0
- package/docs/api/.nojekyll +1 -0
- package/docs/api/assets/hierarchy.js +1 -0
- package/docs/api/assets/highlight.css +92 -0
- package/docs/api/assets/icons.js +18 -0
- package/docs/api/assets/icons.svg +1 -0
- package/docs/api/assets/main.js +60 -0
- package/docs/api/assets/navigation.js +1 -0
- package/docs/api/assets/search.js +1 -0
- package/docs/api/assets/style.css +1633 -0
- package/docs/api/classes/LinkedInClientError.html +37 -0
- package/docs/api/functions/_testGetAccountCookies.html +4 -0
- package/docs/api/functions/_testGetAccountEntry.html +4 -0
- package/docs/api/functions/_testGetAllAccountIds.html +3 -0
- package/docs/api/functions/_testGetPoolState.html +3 -0
- package/docs/api/functions/adminResetAccount.html +1 -0
- package/docs/api/functions/adminSetCooldown.html +1 -0
- package/docs/api/functions/buildCookieHeader.html +1 -0
- package/docs/api/functions/clearAllSmartLeadTokens.html +2 -0
- package/docs/api/functions/clearRequestHistory.html +1 -0
- package/docs/api/functions/clearSessionAccount.html +1 -0
- package/docs/api/functions/clearSmartLeadToken.html +2 -0
- package/docs/api/functions/createEnrichmentClient.html +8 -0
- package/docs/api/functions/extractCsrfToken.html +1 -0
- package/docs/api/functions/extractLinkedInHandle.html +7 -0
- package/docs/api/functions/fetchCookiesFromCosiall.html +14 -0
- package/docs/api/functions/fetchProfileEmailsFromCosiall.html +18 -0
- package/docs/api/functions/forceRefreshCookies.html +1 -0
- package/docs/api/functions/getAccountForSession.html +1 -0
- package/docs/api/functions/getAccountsSummary.html +1 -0
- package/docs/api/functions/getCompaniesBatch.html +5 -0
- package/docs/api/functions/getCompanyById.html +9 -0
- package/docs/api/functions/getCompanyByUrl.html +1 -0
- package/docs/api/functions/getConfig.html +1 -0
- package/docs/api/functions/getCookiePoolHealth.html +1 -0
- package/docs/api/functions/getProfileByUrn.html +17 -0
- package/docs/api/functions/getProfileByVanity.html +10 -0
- package/docs/api/functions/getProfilesBatch.html +1 -0
- package/docs/api/functions/getRequestHistory.html +1 -0
- package/docs/api/functions/getSalesNavigatorProfileDetails.html +1 -0
- package/docs/api/functions/getSalesNavigatorProfileFull.html +16 -0
- package/docs/api/functions/getSmartLeadToken.html +1 -0
- package/docs/api/functions/getSmartLeadTokenCacheStats.html +2 -0
- package/docs/api/functions/getSmartLeadUser.html +2 -0
- package/docs/api/functions/getSnapshot.html +1 -0
- package/docs/api/functions/getYearsAtCompanyOptions.html +2 -0
- package/docs/api/functions/getYearsInPositionOptions.html +2 -0
- package/docs/api/functions/getYearsOfExperienceOptions.html +2 -0
- package/docs/api/functions/incrementMetric.html +1 -0
- package/docs/api/functions/initializeCookiePool.html +1 -0
- package/docs/api/functions/initializeLinkedInClient.html +1 -0
- package/docs/api/functions/isBusinessEmail.html +4 -0
- package/docs/api/functions/isDisposableDomain.html +4 -0
- package/docs/api/functions/isDisposableEmail.html +4 -0
- package/docs/api/functions/isPersonalDomain.html +4 -0
- package/docs/api/functions/isPersonalEmail.html +4 -0
- package/docs/api/functions/isRoleAccount.html +4 -0
- package/docs/api/functions/isValidEmailSyntax.html +4 -0
- package/docs/api/functions/parseFullProfile.html +15 -0
- package/docs/api/functions/parseSalesSearchResults.html +1 -0
- package/docs/api/functions/reportAccountFailure.html +1 -0
- package/docs/api/functions/reportAccountSuccess.html +1 -0
- package/docs/api/functions/resolveCompanyUniversalName.html +1 -0
- package/docs/api/functions/searchSalesLeads.html +16 -0
- package/docs/api/functions/selectAccountForRequest.html +1 -0
- package/docs/api/functions/setAccountForSession.html +1 -0
- package/docs/api/functions/typeahead.html +1 -0
- package/docs/api/functions/verifyEmailMx.html +1 -0
- package/docs/api/hierarchy.html +1 -0
- package/docs/api/index.html +12 -0
- package/docs/api/interfaces/AccountCookies.html +4 -0
- package/docs/api/interfaces/BatchEnrichmentOptions.html +14 -0
- package/docs/api/interfaces/CacheAdapter.html +6 -0
- package/docs/api/interfaces/CanonicalEmail.html +14 -0
- package/docs/api/interfaces/Company.html +17 -0
- package/docs/api/interfaces/ConstructConfig.html +8 -0
- package/docs/api/interfaces/CosiallProfileEmailsResponse.html +11 -0
- package/docs/api/interfaces/DropcontactConfig.html +3 -0
- package/docs/api/interfaces/EnrichmentCandidate.html +34 -0
- package/docs/api/interfaces/EnrichmentClient.html +10 -0
- package/docs/api/interfaces/EnrichmentClientConfig.html +12 -0
- package/docs/api/interfaces/EnrichmentLogger.html +6 -0
- package/docs/api/interfaces/EnrichmentOptions.html +10 -0
- package/docs/api/interfaces/HunterConfig.html +3 -0
- package/docs/api/interfaces/LddConfig.html +4 -0
- package/docs/api/interfaces/LddProfileData.html +6 -0
- package/docs/api/interfaces/LinkedInClientConfig.html +20 -0
- package/docs/api/interfaces/LinkedInCookie.html +9 -0
- package/docs/api/interfaces/LinkedInPosition.html +14 -0
- package/docs/api/interfaces/LinkedInProfile.html +21 -0
- package/docs/api/interfaces/LinkedInSpotlightBadge.html +5 -0
- package/docs/api/interfaces/LinkedInTenure.html +3 -0
- package/docs/api/interfaces/Metrics.html +22 -0
- package/docs/api/interfaces/MetricsSnapshot.html +23 -0
- package/docs/api/interfaces/ProfileEducation.html +8 -0
- package/docs/api/interfaces/ProfileEmailsLookupOptions.html +9 -0
- package/docs/api/interfaces/ProfilePosition.html +12 -0
- package/docs/api/interfaces/ProfileSkill.html +3 -0
- package/docs/api/interfaces/ProviderResult.html +11 -0
- package/docs/api/interfaces/ProvidersConfig.html +17 -0
- package/docs/api/interfaces/RequestHistoryEntry.html +8 -0
- package/docs/api/interfaces/SalesLeadSearchResult.html +31 -0
- package/docs/api/interfaces/SalesNavigatorContactInfo.html +5 -0
- package/docs/api/interfaces/SalesNavigatorPosition.html +11 -0
- package/docs/api/interfaces/SalesNavigatorProfile.html +9 -0
- package/docs/api/interfaces/SalesNavigatorProfileFull.html +24 -0
- package/docs/api/interfaces/SearchSalesResult.html +5 -0
- package/docs/api/interfaces/SmartLeadAuthConfig.html +6 -0
- package/docs/api/interfaces/SmartLeadCredentials.html +3 -0
- package/docs/api/interfaces/SmartLeadLoginResponse.html +3 -0
- package/docs/api/interfaces/SmartLeadUser.html +8 -0
- package/docs/api/interfaces/SmartProspectConfig.html +19 -0
- package/docs/api/interfaces/SmartProspectContact.html +24 -0
- package/docs/api/interfaces/SmartProspectSearchFilters.html +42 -0
- package/docs/api/interfaces/TypeaheadItem.html +3 -0
- package/docs/api/interfaces/TypeaheadResult.html +3 -0
- package/docs/api/interfaces/VerificationResult.html +16 -0
- package/docs/api/types/CostCallback.html +2 -0
- package/docs/api/types/Geo.html +4 -0
- package/docs/api/types/LddApiResponse.html +1 -0
- package/docs/api/types/ProviderFunc.html +2 -0
- package/docs/api/types/ProviderName.html +2 -0
- package/docs/api/types/SalesSearchFilters.html +7 -0
- package/docs/api/types/TypeaheadType.html +1 -0
- package/docs/api/variables/COMPANY_SIZE_OPTIONS.html +1 -0
- package/docs/api/variables/DEFAULT_PROVIDER_ORDER.html +20 -0
- package/docs/api/variables/DISPOSABLE_DOMAINS.html +2 -0
- package/docs/api/variables/FUNCTION_OPTIONS.html +1 -0
- package/docs/api/variables/INDUSTRY_OPTIONS.html +1 -0
- package/docs/api/variables/LANGUAGE_OPTIONS.html +1 -0
- package/docs/api/variables/PERSONAL_DOMAINS.html +2 -0
- package/docs/api/variables/PROVIDER_COSTS.html +15 -0
- package/docs/api/variables/REGION_OPTIONS.html +1 -0
- package/docs/api/variables/SENIORITY_OPTIONS.html +3 -0
- package/docs/api/variables/YEARS_OPTIONS.html +1 -0
- package/docs/index.html +98 -0
- package/package.json +16 -5
|
@@ -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
|
+
```
|