
Security News
npm Adopts OIDC for Trusted Publishing in CI/CD Workflows
npm now supports Trusted Publishing with OIDC, enabling secure package publishing directly from CI/CD workflows without relying on long-lived tokens.
@lineai/linkedin-api
Advanced tools
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
Professional LinkedIn API client with TypeScript support, entity classes, and developer-friendly features. Implements the LinkedIn Data API from RapidAPI.
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 |
import LinkedInAPI, {
LOCATIONS,
INDUSTRIES,
COMPANY_SIZES,
LinkedInError,
RateLimitError,
NotFoundError
} from '@lineai/linkedin-api';
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 |
PostSearchItemAuthorData
interface for better type safety when working with post search resultsuniversalName
โ username
transformation to occur in CompanySearchResult.items getter for better data consistencyapi.getProfile(item.username)
and api.getCompany(item.username)
now use the same field nameusername
instead of universalName
for consistency with profile searchnumber
instead of string
get
methods with 100% cache hit performancenpm install @lineai/linkedin-api
# or
yarn add @lineai/linkedin-api
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"
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
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);
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());
// 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;
}
}
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);
}
}
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);
}
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
});
All search methods now support intelligent retry logic to ensure you get the results you need:
// 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
// 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
});
// 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
});
// 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?
// 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()}`);
});
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 |
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 |
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 |
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 |
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 results return raw data objects. Use the API methods to get full details:
// 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 | 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 |
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');
}
}
LOCATIONS.US.NEW_YORK // 105080838 (number)
LOCATIONS.US.SAN_FRANCISCO // 102277331 (number)
LOCATIONS.US.LOS_ANGELES // 102448103 (number)
// ... more cities available
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.TECHNOLOGY // 96
INDUSTRIES.FINANCIAL_SERVICES // 43
INDUSTRIES.HEALTHCARE // 14
INDUSTRIES.RETAIL // 27
// ... more industries available
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 |
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 |
// 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);
}
LinkedIn profile searches may show high total counts but return 0 items due to private profiles. The package automatically handles this:
// 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'
});
The API client supports several configuration options:
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);
The API includes rate limit information in responses. Handle rate limits gracefully:
try {
const profile = await api.getProfile('username');
} catch (error) {
if (error instanceof RateLimitError) {
console.log(`Rate limited until: ${error.retryAfter}`);
// Implement retry logic
}
}
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 |
See API_IMPLEMENTATION_GUIDE.md for implementation details and patterns.
MIT
FAQs
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
We found that @lineai/linkedin-api demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago.ย It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
npm now supports Trusted Publishing with OIDC, enabling secure package publishing directly from CI/CD workflows without relying on long-lived tokens.
Research
/Security News
A RubyGems malware campaign used 60 malicious packages posing as automation tools to steal credentials from social media and marketing tool users.
Security News
The CNA Scorecard ranks CVE issuers by data completeness, revealing major gaps in patch info and software identifiers across thousands of vulnerabilities.