UNPKG

@lineai/linkedin-api

Version:

Professional LinkedIn API client with TypeScript support, entity classes, and developer-friendly features. Perfect for AI coders, recruiting, lead generation, market research, and content analysis. Includes comprehensive JSDoc, helper constants (LOCATIONS

668 lines (529 loc) โ€ข 23.7 kB
# @lineai/linkedin-api Professional LinkedIn API client with TypeScript support, entity classes, and developer-friendly features. Implements the [LinkedIn Data API from RapidAPI](https://rapidapi.com/rockapis-rockapis-default/api/linkedin-data-api). ## Quick Reference | Operation | Method | Purpose | Key Parameters | Returns | |-----------|--------|---------|----------------|---------| | **Profile Search** | `searchProfiles(params)` | Find profiles by criteria | `keywords`, `geo`, `company`, `minItems` | `ProfileSearchResult` | | **Profile Get** | `getProfile(username)` | Full profile data | `username` | `LinkedInProfile` | | **Company Search** | `searchCompanies(params)` | Find companies by criteria | `keywords`, `industries`, `companySizes`, `minItems` | `CompanySearchResult` | | **Company Get** | `getCompany(username)` | Full company data | `username` | `LinkedInCompany` | | **Post Search** | `searchPosts(params)` | Find posts by criteria | `keywords`, `author`, `datePosted`, `minItems` | `PostSearchResult` | | **Post Get** | `getPost(postId)` | Full post data | `postId` | `LinkedInPost` | ### Essential Imports ```javascript import LinkedInAPI, { LOCATIONS, INDUSTRIES, COMPANY_SIZES, LinkedInError, RateLimitError, NotFoundError } from '@lineai/linkedin-api'; ``` ### Core Entity Methods | Entity | Key Methods | Returns | |--------|-------------|---------| | **LinkedInProfile** | `getFullName()`, `getHeadline()`, `getCurrentPosition()`, `getLinkedInUrl()` | Formatted strings/objects | | **LinkedInCompany** | `getName()`, `getFollowerCount()`, `getEmployeeCount()`, `getLinkedInUrl()` | Company data | | **LinkedInPost** | `getText()`, `getLikeCount()`, `getCommentCount()`, `getAuthor()` | Post metrics | ## ๐Ÿ†• What's New in v1.3.8 - ๐Ÿ” **Improved TypeScript Types** - Added `PostSearchItemAuthorData` interface for better type safety when working with post search results - ๐ŸŽฏ **Enhanced IntelliSense** - Post search author data now has proper autocompletion and type checking - ๐Ÿ› ๏ธ **Type Safety Fix** - Eliminates type mismatches between post search results and full post data author schemas ### v1.3.7 Features - ๐Ÿ”ง **Next.js Build Fix** - Fixed webpack compilation error by replacing process.env.NODE_ENV manipulation with instance-based debug mode - โšก **Better Debug Mode** - Debug logging now uses instance property instead of environment variable modification ### v1.3.6 Features - ๐Ÿ“ฆ **Package Optimization** - Removed examples directory to reduce package size and improve build compatibility - ๐Ÿ› ๏ธ **Build Fixes** - Resolved Next.js build issues by streamlining package structure ### v1.3.5 Features - ๐Ÿงน **Dependency Cleanup** - Removed Prettier and related ESLint configurations to reduce package size and simplify development - โšก **Streamlined Build** - Cleaner development environment with fewer dependencies ### v1.3.4 Features - ๐Ÿ› ๏ธ **Data Transformation Fix** - Fixed company search `universalName` โ†’ `username` transformation to occur in CompanySearchResult.items getter for better data consistency - ๐ŸŽฏ **Unified API Experience** - Both `api.getProfile(item.username)` and `api.getCompany(item.username)` now use the same field name ### v1.3.3 Features - ๐Ÿ”„ **Consistent Username Field** - Company search results now use `username` instead of `universalName` for consistency with profile search ### v1.3.2 Features - ๐Ÿ—‘๏ธ **Simplified Search Results** - Removed ProfileSearchItem, CompanySearchItem, and PostSearchItem wrapper classes for direct data access - ๐ŸŽฏ **Direct Data Access** - Search results now return raw data objects with helper methods for cleaner, simpler usage ### v1.3.1 Features - ๐Ÿ”ง **Fixed Location Parameter Types** - LocationId is now correctly typed as `number` instead of `string` - โœ… **API Compatibility** - Location constants (LOCATIONS) now properly work with LinkedIn API requirements - ๐ŸŽฏ **Cross-Method Consistency** - Both profile search (GET) and company search (POST) handle location arrays correctly ### v1.3.0 Features - โœจ **Enhanced Type Safety** - Complete TypeScript response type definitions prevent runtime errors - ๐Ÿ”’ **Immutable Parameters** - Search parameters are never mutated, ensuring predictable behavior - ๐ŸŽ๏ธ **Improved Caching** - Now works for all `get` methods with 100% cache hit performance - ๐Ÿ› ๏ธ **Better Error Handling** - More specific error types with better debugging information - ๐ŸŽฏ **Unified API** - Consistent pagination and parameter handling across all search methods - ๐Ÿ” **Enhanced Debugging** - Improved debug logs with accurate response structure information ## Features - ๐Ÿš€ **Easy to use** - Simple, intuitive API with high-level methods - ๐Ÿ“˜ **TypeScript ready** - Full type definitions included - ๐ŸŽฏ **Entity classes** - Clean data access without dealing with raw JSON - ๐Ÿ”„ **Seamless data loading** - Load full profiles/companies/posts from search results - ๐Ÿ›ก๏ธ **Safe defaults** - Never worry about null/undefined values - โšก **Built-in caching** - Reduce API calls automatically (v1.3.0+) - ๐ŸŽ›๏ธ **Pagination helpers** - Easy navigation through search results - ๐Ÿท๏ธ **Helper constants** - No more magic strings for locations, industries, etc. - ๐Ÿ”’ **Parameter immutability** - Original search params never modified (v1.3.0+) ## Installation ```bash npm install @lineai/linkedin-api # or yarn add @lineai/linkedin-api ``` ## Quick Start ```javascript import { LinkedInAPI } from '@lineai/linkedin-api'; const api = new LinkedInAPI('your-rapidapi-key'); // Get a profile const profile = await api.getProfile('satyanadella'); console.log(profile.getFullName()); // "Satya Nadella" console.log(profile.getCurrentPosition()?.title); // "CEO" ``` ## Usage Examples ### Profile Operations ```javascript import { LinkedInAPI, LOCATIONS } from '@lineai/linkedin-api'; const api = new LinkedInAPI('your-rapidapi-key'); // Search for profiles const searchResults = await api.searchProfiles({ keywords: 'software engineer', geo: [LOCATIONS.US.SAN_FRANCISCO, LOCATIONS.US.NEW_YORK], // Auto-joined company: 'Google' }); console.log(`Found ${searchResults.total} profiles`); // Access search results searchResults.items.forEach(item => { console.log(item.getFullName()); console.log(item.getHeadline()); console.log(item.getLinkedInUrl()); }); // Get full profile using username from search result const firstResult = searchResults.items[0]; const fullProfile = await api.getProfile(firstResult.username); // Access full profile data console.log(fullProfile.getFullName()); console.log(fullProfile.getCurrentPosition()?.companyName); console.log(fullProfile.getEducation()[0]?.schoolName); console.log(fullProfile.getSkills().length); console.log(fullProfile.getProfilePictureUrl('large')); // Direct profile access const profile = await api.getProfile('billgates'); console.log(profile.getLinkedInUrl()); // https://linkedin.com/in/billgates ``` ### Company Operations ```javascript import { LinkedInAPI, COMPANY_SIZES, INDUSTRIES, LOCATIONS } from '@lineai/linkedin-api'; const api = new LinkedInAPI('your-rapidapi-key'); // Search companies const companies = await api.searchCompanies({ keyword: 'artificial intelligence', locations: [LOCATIONS.US.ALL], industries: [INDUSTRIES.TECHNOLOGY], companySizes: [COMPANY_SIZES.LARGE, COMPANY_SIZES.XLARGE], // 201-1000 employees hasJobs: true }); // Access company data const company = companies.items[0]; console.log(company.getName()); console.log(company.getTagline()); // Get full company details using username from search result const fullCompany = await api.getCompany(company.username); console.log(fullCompany.getEmployeeCount()); console.log(fullCompany.getHeadquarters()?.city); console.log(fullCompany.getSpecialties()); console.log(fullCompany.getFollowerCount()); console.log(fullCompany.getWebsite()); // Direct company access const microsoft = await api.getCompany('microsoft'); console.log(microsoft.getName()); // "Microsoft" console.log(microsoft.getEmployeeCount()); console.log(microsoft.getAllLocations().length); ``` ### Post Operations ```javascript const api = new LinkedInAPI('your-rapidapi-key'); // Search posts const posts = await api.searchPosts({ keyword: 'machine learning', sortBy: 'date_posted', fromCompany: [1441] // Google's company ID }); console.log(`Found ${posts.total} posts`); // Access post data const post = posts.items[0]; console.log(post.getText()); console.log(post.getAuthorName()); console.log(post.getPostedAt()); // Get full post details using URN from search result const fullPost = await api.getPost(post.urn); console.log(fullPost.getTotalEngagement()); console.log(fullPost.getLikeCount()); console.log(fullPost.getCommentsCount()); console.log(fullPost.hasVideo()); console.log(fullPost.getArticle()?.title); // Direct post access const specificPost = await api.getPost('7219434359085252608'); console.log(specificPost.getAuthor().firstName); console.log(specificPost.getTotalEngagement()); ``` ### Pagination ```javascript // Handle pagination easily const results = await api.searchProfiles({ keywords: 'CEO' }); console.log(`Page 1: ${results.items.length} items`); if (results.hasNextPage()) { const page2 = await results.getNextPage(); console.log(`Page 2: ${page2.items.length} items`); } // Process all pages let currentPage = results; let pageNum = 1; while (currentPage.items.length > 0) { console.log(`Processing page ${pageNum}: ${currentPage.items.length} items`); // Process items for (const item of currentPage.items) { console.log(item.getFullName()); } if (currentPage.hasNextPage()) { currentPage = await currentPage.getNextPage(); pageNum++; } else { break; } } ``` ### Error Handling ```javascript import { LinkedInAPI, NotFoundError, RateLimitError } from '@lineai/linkedin-api'; const api = new LinkedInAPI('your-rapidapi-key'); try { const profile = await api.getProfile('invalid-username-xyz'); } catch (error) { if (error instanceof NotFoundError) { console.error('Profile not found'); } else if (error instanceof RateLimitError) { console.error(`Rate limited. Retry after: ${error.retryAfter}`); } else { console.error('Unexpected error:', error.message); } } ``` ### TypeScript Support ```typescript import { LinkedInAPI, LinkedInProfile, LinkedInCompany, ProfileSearchParams, LOCATIONS, LocationId } from '@lineai/linkedin-api'; const api = new LinkedInAPI('your-key'); // Full type safety const searchParams: ProfileSearchParams = { keywords: 'engineer', geo: [LOCATIONS.US.SEATTLE] as LocationId[] }; const results = await api.searchProfiles(searchParams); // TypeScript knows all available methods const profile: LinkedInProfile = await api.getProfile('username'); const position = profile.getCurrentPosition(); // Type: Position | null if (position) { console.log(position.title); // TypeScript knows all Position properties console.log(position.companyName); } ``` ### Helper Constants ```javascript import { LOCATIONS, COMPANY_SIZES, INDUSTRIES } from '@lineai/linkedin-api'; // Use location constants instead of numeric IDs const usProfiles = await api.searchProfiles({ geo: [LOCATIONS.US.ALL] }); const techCompanies = await api.searchCompanies({ locations: [ LOCATIONS.US.SAN_FRANCISCO, LOCATIONS.US.SEATTLE, LOCATIONS.US.AUSTIN ], industries: [INDUSTRIES.TECHNOLOGY], companySizes: COMPANY_SIZES.ALL // All company sizes }); ``` ## ๐Ÿ†• Smart Retry with minItems (v1.2.2) All search methods now support intelligent retry logic to ensure you get the results you need: ### Profile Search with minItems ```javascript // Get at least 15 profiles - will retry across different pages until found const profiles = await api.searchProfiles({ keywords: 'software engineer', geo: [LOCATIONS.US.SAN_FRANCISCO], minItems: 15, // Minimum results required maxRetries: 5 // Maximum retry attempts }); console.log(`Found ${profiles.items.length} profiles`); // At least 15 ``` ### Company Search with minItems ```javascript // Get substantial company dataset const companies = await api.searchCompanies({ keywords: 'fintech startup', industries: [INDUSTRIES.FINANCIAL_SERVICES], minItems: 20, // Keep retrying until 20+ companies found maxRetries: 6 }); ``` ### Post Search with minItems ```javascript // Collect many posts for content analysis const posts = await api.searchPosts({ keywords: 'artificial intelligence', datePosted: 'past-week', minItems: 25, // Need at least 25 posts maxRetries: 8 }); ``` ### Disable Retry Logic ```javascript // For exact pagination control, disable retries const exactPage = await api.searchProfiles({ keywords: 'manager', start: '20', minItems: 0 // Disable retries - return exact page results }); ``` **Why use minItems?** - LinkedIn has many private profiles/companies that appear in counts but don't return data - Results can be sparse across different pages - Perfect for data collection, analysis, or when you need substantial sample sizes - Automatically handles pagination gaps and empty result pages ### Advanced Usage ```javascript // Access raw data when needed const profile = await api.getProfile('username'); console.log(profile.raw); // Original API response // Use the low-level endpoint method const response = await api.callEndpoint('get_profile_posts', { username: 'satyanadella', start: '0' }); // Cache management api.clearCache(); // Clear all cached data api.enableCache = false; // Disable caching // Batch operations const usernames = ['billgates', 'satyanadella', 'sundarpichai']; const profiles = await Promise.all( usernames.map(username => api.getProfile(username)) ); profiles.forEach(profile => { console.log(`${profile.getFullName()} - ${profile.getHeadline()}`); }); ``` ## API Reference ### Main Class: LinkedInAPI ```javascript const api = new LinkedInAPI(rapidApiKey, rapidApiHost?) ``` | Method | Parameters | Returns | Description | |--------|------------|---------|-------------| | `searchProfiles(params)` | `{keywords?, geo?, company?, start?, minItems?, maxRetries?}` | `ProfileSearchResult` | Search LinkedIn profiles with smart retry | | `getProfile(username)` | `string` | `LinkedInProfile` | Get full profile data | | `searchCompanies(params)` | `{keywords?, industries?, companySizes?, minItems?, maxRetries?}` | `CompanySearchResult` | Search LinkedIn companies with smart retry | | `getCompany(username)` | `string` | `LinkedInCompany` | Get full company data | | `searchPosts(params)` | `{keywords?, author?, datePosted?, minItems?, maxRetries?}` | `PostSearchResult` | Search LinkedIn posts with smart retry | | `getPost(postId)` | `string` | `LinkedInPost` | Get full post data | | `clearCache()` | - | `void` | Clear all cached data | | `validateParameters(endpoint, params)` | `string, object` | `ValidationResult` | Validate parameters | ### Entity Classes #### LinkedInProfile | Method | Returns | Description | |--------|---------|-------------| | `getFullName()` | `string` | Combined first + last name | | `getHeadline()` | `string` | Professional headline | | `getCurrentPosition()` | `Position \| null` | Current job position | | `getAllPositions()` | `Position[]` | All work experience | | `getEducation()` | `Education[]` | Education history | | `getSkills()` | `Skill[]` | Skills list | | `getLinkedInUrl()` | `string` | Full LinkedIn profile URL | | `getLocation()` | `Location` | Geographic location | | `getProfilePictureUrl(size?)` | `string` | Profile picture URL | #### LinkedInCompany | Method | Returns | Description | |--------|---------|-------------| | `getName()` | `string` | Company name | | `getFollowerCount()` | `number` | LinkedIn followers | | `getEmployeeCount()` | `number` | Employee count | | `getLinkedInUrl()` | `string` | Full LinkedIn company URL | | `getDescription()` | `string` | Company description | | `getSpecialties()` | `string[]` | Company specialties | | `getIndustries()` | `string[]` | Industry categories | | `getWebsite()` | `string` | Company website | #### LinkedInPost | Method | Returns | Description | |--------|---------|-------------| | `getText()` | `string` | Post content text | | `getLikeCount()` | `number` | Number of likes | | `getCommentCount()` | `number` | Number of comments | | `getShareCount()` | `number` | Number of shares | | `getAuthor()` | `Author` | Post author info | | `getPostedAt()` | `Date` | Post publication date | | `getTotalEngagement()` | `number` | Total engagement count | | `hasMedia()` | `boolean` | Contains images/videos | ### Search Results Classes All search result classes support pagination: | Method | Returns | Description | |--------|---------|-------------| | `hasNextPage()` | `boolean` | More results available | | `getNextPage()` | `Promise<SearchResult>` | Fetch next page | | `items` | `SearchItem[]` | Current page items | | `total` | `number` | Total result count | ### Search Item Data Search results return raw data objects. Use the API methods to get full details: ```javascript // Get full data from search results const profiles = await api.searchProfiles({ keywords: 'engineer' }); const fullProfile = await api.getProfile(profiles.items[0].username); const companies = await api.searchCompanies({ keyword: 'tech' }); const fullCompany = await api.getCompany(companies.items[0].username); const posts = await api.searchPosts({ keyword: 'AI' }); const fullPost = await api.getPost(posts.items[0].urn); ``` ## Error Types | Error | When Thrown | Properties | Example Handling | |-------|-------------|------------|------------------| | `LinkedInError` | Base error class | `message`, `code` | Generic error handling | | `RateLimitError` | Rate limit exceeded | `retryAfter` (Date) | Wait until retry time | | `NotFoundError` | Resource not found/private | `message` | Handle missing data | | `NetworkError` | Connection issues | `originalError` | Retry logic | ```javascript try { const profile = await api.getProfile('username'); } catch (error) { if (error instanceof RateLimitError) { console.log(`Retry after: ${error.retryAfter}`); } else if (error instanceof NotFoundError) { console.log('Profile not found or private'); } } ``` ## Helper Constants ### LOCATIONS ```javascript LOCATIONS.US.NEW_YORK // 105080838 (number) LOCATIONS.US.SAN_FRANCISCO // 102277331 (number) LOCATIONS.US.LOS_ANGELES // 102448103 (number) // ... more cities available ``` ### COMPANY_SIZES ```javascript COMPANY_SIZES.SELF_EMPLOYED // 'A' (Self-employed) COMPANY_SIZES.TINY // 'B' (1-10 employees) COMPANY_SIZES.SMALL // 'C' (11-50 employees) COMPANY_SIZES.MEDIUM // 'D' (51-200 employees) COMPANY_SIZES.LARGE // 'E' (201-500 employees) COMPANY_SIZES.XLARGE // 'F' (501-1000 employees) COMPANY_SIZES.ENTERPRISE // 'G' (1001-5000 employees) COMPANY_SIZES.MASSIVE // 'H' (5001-10000 employees) COMPANY_SIZES.MEGA // 'I' (10000+ employees) ``` ### INDUSTRIES ```javascript INDUSTRIES.TECHNOLOGY // 96 INDUSTRIES.FINANCIAL_SERVICES // 43 INDUSTRIES.HEALTHCARE // 14 INDUSTRIES.RETAIL // 27 // ... more industries available ``` ## Troubleshooting ### Common Issues & Solutions | Problem | Cause | Solution | |---------|-------|----------| | **"Invalid username"** | Username format incorrect | Use LinkedIn username (from URL) not display name | | **"Rate limit exceeded"** | Too many requests | Implement delays, use caching, handle RateLimitError | | **"Profile not found"** | Private profile or invalid username | Handle NotFoundError gracefully | | **Empty search results** | Overly specific criteria | Broaden search parameters | | **Profile search returns 0 items** | Private profiles in result set | Enable auto-retry (default) or use debug mode | | **Slow performance** | Not using caching | Enable caching (default) or clear periodically | | **Memory issues** | Large dataset processing | Process in batches, clear cache regularly | ### Best Practices | Practice | Why | Example | |----------|-----|---------| | **Use helper constants** | Avoid magic strings | `geo: [LOCATIONS.US.NYC]` not `geo: ['105080838']` | | **Handle errors specifically** | Better error recovery | Check for `RateLimitError`, `NotFoundError` | | **Use entity methods** | Safe data access | `profile.getFullName()` not `profile.data.firstName` | | **Implement delays** | Avoid rate limits | `await new Promise(r => setTimeout(r, 1000))` | | **Load full data smartly** | Performance optimization | Search first, then load full data only when needed | ### Parameter Validation ```javascript // Validate before making requests const validation = api.validateParameters('search_people', { keywords: 'engineer' }); if (!validation.valid) { console.log('Missing:', validation.missingRequired); console.log('Extra:', validation.extraParams); } ``` ### Profile Search & Private Profiles LinkedIn profile searches may show high total counts but return 0 items due to private profiles. The package automatically handles this: ```javascript // Automatic retry (default behavior) const results = await api.searchProfiles({ keywords: 'engineer' }); // Will try different start positions automatically // Debug what's happening api.setDebugMode(true); const debugResults = await api.searchProfiles({ keywords: 'ceo' }); // Logs: "Profile search attempt 1, start=0: itemsCount=0" // "Profile search attempt 2, start=10: itemsCount=2" โœ… // Manual control const exactResults = await api.searchProfiles({ keywords: 'manager', autoRetryForPrivateProfiles: false, // Get exact page start: '20' }); ``` ## Configuration The API client supports several configuration options: ```javascript const api = new LinkedInAPI('your-key'); // Disable caching api.enableCache = false; // Adjust cache timeout (default: 5 minutes) api.cacheTimeout = 10 * 60 * 1000; // 10 minutes // Clear cache api.clearCache(); // Enable debug mode (helpful for troubleshooting) api.setDebugMode(true); ``` ## Rate Limiting The API includes rate limit information in responses. Handle rate limits gracefully: ```javascript try { const profile = await api.getProfile('username'); } catch (error) { if (error instanceof RateLimitError) { console.log(`Rate limited until: ${error.retryAfter}`); // Implement retry logic } } ``` ## Additional Resources - **[AI_USAGE_GUIDE.md](./AI_USAGE_GUIDE.md)** - Comprehensive guide optimized for AI coders with advanced patterns - **[API_IMPLEMENTATION_GUIDE.md](./API_IMPLEMENTATION_GUIDE.md)** - Implementation details and development patterns - **[LinkedIn Data API Documentation](https://rapidapi.com/rockapis-rockapis-default/api/linkedin-data-api)** - Original RapidAPI documentation ## Use Cases | Scenario | Methods Needed | Pattern | |----------|----------------|---------| | **Recruiting** | `searchProfiles()`, `getProfile()` | Search โ†’ Filter โ†’ Load Full Data | | **Lead Generation** | `searchCompanies()`, `getCompany()` | Search by Industry โ†’ Get Details | | **Market Research** | `searchCompanies()`, `searchProfiles()` | Industry Analysis + Employee Insights | | **Content Analysis** | `searchPosts()`, `getPost()` | Keyword Search โ†’ Engagement Analysis | | **Competitive Intelligence** | `getCompany()`, `searchProfiles()` | Company Data + Employee Profiles | ## Contributing See [API_IMPLEMENTATION_GUIDE.md](./API_IMPLEMENTATION_GUIDE.md) for implementation details and patterns. ## License MIT