@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
Markdown
# /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 '/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 /linkedin-api
# or
yarn add /linkedin-api
```
## Quick Start
```javascript
import { LinkedInAPI } from '/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 '/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 '/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 '/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 '/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 '/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