How to Use LinkedIn API: Complete Professional Network Integration Guide (2026)

Master LinkedIn API integration with this complete guide. Learn OAuth 2.0, profile access, content posting, company pages, ads API, and production strategies.

Ashley Innocent

Ashley Innocent

25 March 2026

How to Use LinkedIn API: Complete Professional Network Integration Guide (2026)

TL;DR

The LinkedIn API enables developers to integrate with LinkedIn’s professional network programmatically. It uses OAuth 2.0 authentication, RESTful and GraphQL endpoints for profiles, posts, comments, company pages, and ads, with rate limits of 100-500 requests per day per app. This guide covers authentication setup, profile access, content posting, company page management, ads API, and production integration strategies.

Introduction

LinkedIn has over 900 million professional users across 200+ countries. For developers building recruiting tools, marketing platforms, or B2B applications, LinkedIn API integration is essential for reaching this professional audience.

Here’s the reality: B2B marketers managing LinkedIn presence manually lose 15-20 hours weekly on posting, engagement tracking, and lead generation. A solid LinkedIn API integration automates content distribution, lead capture, engagement analytics, and recruitment workflows.

This guide walks through the complete LinkedIn API integration process. You’ll learn OAuth 2.0 authentication, profile access, content posting, company page management, ads integration, webhooks, and production deployment strategies. By the end, you’ll have a production-ready LinkedIn integration.

💡
Apidog simplifies API integration testing. Test your LinkedIn endpoints, validate OAuth flows, inspect API responses, and debug permission issues in one workspace. Import API specifications, mock responses, and share test scenarios with your team.
button

What Is the LinkedIn API?

LinkedIn provides RESTful and GraphQL APIs for accessing professional network data. The API handles:

Key Features

Feature Description
RESTful + GraphQL Multiple API styles
OAuth 2.0 User authorization required
Rate Limiting 100-500 requests/day per app
Company Pages Full CRUD operations
Ads API Campaign management
Webhooks Real-time notifications
Media Upload Images and videos

API Products

API Access Level Use Case
Sign In with LinkedIn Open User authentication
Profile API Partner Read user profile
Company Admin API Partner Manage company pages
Ads API Partner Ad campaign management
Job Posting API Partner Post and manage jobs
Marketing Developer Platform Partner Full API access

API Versions

Version Status End Date
v2 Current Active
v1 Retired December 2023

Getting Started: Authentication Setup

Step 1: Create LinkedIn Developer Account

Before accessing the API:

  1. Visit the LinkedIn Developer Portal
  2. Sign in with LinkedIn account
  3. Click Create App in My Apps dashboard
  4. Fill in app details (name, logo, description)

Step 2: Configure App Settings

Set up app authentication:

const LINKEDIN_CLIENT_ID = process.env.LINKEDIN_CLIENT_ID;
const LINKEDIN_CLIENT_SECRET = process.env.LINKEDIN_CLIENT_SECRET;
const LINKEDIN_REDIRECT_URI = process.env.LINKEDIN_REDIRECT_URI;

// Get these from your app dashboard
// https://www.linkedin.com/developers/apps/{appId}/auth

Step 3: Request Required Permissions

LinkedIn requires permission approval:

Permission Description Approval Required
r_liteprofile Basic profile (name, photo) Auto-approved
r_emailaddress Email address Auto-approved
w_member_social Post on behalf of user Partner verification
r_basicprofile Full profile Partner verification
r_organization_social Company page access Partner verification
w_organization_social Post to company page Partner verification
rw_ads Ads management Partner verification
r_ads_reporting Ads analytics Partner verification

Step 4: Build Authorization URL

Implement OAuth 2.0 flow:

const getAuthUrl = (state, scopes = ['r_liteprofile', 'r_emailaddress']) => {
  const params = new URLSearchParams({
    response_type: 'code',
    client_id: LINKEDIN_CLIENT_ID,
    redirect_uri: LINKEDIN_REDIRECT_URI,
    scope: scopes.join(' '),
    state: state
  });

  return `https://www.linkedin.com/oauth/v2/authorization?${params.toString()}`;
};

// Usage
const state = require('crypto').randomBytes(16).toString('hex');
const authUrl = getAuthUrl(state, ['r_liteprofile', 'r_emailaddress', 'w_member_social']);
console.log(`Redirect user to: ${authUrl}`);

Step 5: Exchange Code for Access Token

Handle OAuth callback:

const exchangeCodeForToken = async (code) => {
  const response = await fetch('https://www.linkedin.com/oauth/v2/accessToken', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded'
    },
    body: new URLSearchParams({
      grant_type: 'authorization_code',
      code: code,
      client_id: LINKEDIN_CLIENT_ID,
      client_secret: LINKEDIN_CLIENT_SECRET,
      redirect_uri: LINKEDIN_REDIRECT_URI
    })
  });

  const data = await response.json();

  return {
    accessToken: data.access_token,
    expiresIn: data.expires_in // 60 days
  };
};

// Handle callback
app.get('/oauth/callback', async (req, res) => {
  const { code, state } = req.query;

  try {
    const tokens = await exchangeCodeForToken(code);

    // Store tokens securely
    await db.users.update(req.session.userId, {
      linkedin_access_token: tokens.accessToken,
      linkedin_token_expires: Date.now() + (tokens.expiresIn * 1000)
    });

    res.redirect('/success');
  } catch (error) {
    console.error('OAuth error:', error);
    res.status(500).send('Authentication failed');
  }
});

Step 6: Refresh Access Token

Access tokens expire after 60 days:

const refreshAccessToken = async (refreshToken) => {
  // Note: LinkedIn doesn't provide refresh tokens
  // Users must re-authenticate after 60 days
  // Implement expiry notification
};

// Check token expiry before API calls
const ensureValidToken = async (userId) => {
  const user = await db.users.findById(userId);

  if (user.linkedin_token_expires < Date.now() + 86400000) { // 24 hours
    // Notify user to re-authenticate
    await notifyUserToReauth(user.id);
    throw new Error('Token expired, please re-authenticate');
  }

  return user.linkedin_access_token;
};

Step 7: Make Authenticated API Calls

Create reusable API client:

const LINKEDIN_BASE_URL = 'https://api.linkedin.com/v2';

const linkedinRequest = async (endpoint, options = {}) => {
  const accessToken = await ensureValidToken(options.userId);

  const response = await fetch(`${LINKEDIN_BASE_URL}${endpoint}`, {
    ...options,
    headers: {
      'Authorization': `Bearer ${accessToken}`,
      'Content-Type': 'application/json',
      'X-Restli-Protocol-Version': '2.0.0',
      ...options.headers
    }
  });

  if (!response.ok) {
    const error = await response.json();
    throw new Error(`LinkedIn API Error: ${error.message}`);
  }

  return response.json();
};

// Usage
const profile = await linkedinRequest('/me');
console.log(`Hello, ${profile.localizedFirstName} ${profile.localizedLastName}`);

Profile Access

Getting User Profile

Fetch authenticated user’s profile:

const getUserProfile = async () => {
  const response = await linkedinRequest('/me?projection=(id,firstName,lastName,profilePicture(displayImage~:playableStreams))');
  return response;
};

// Usage
const profile = await getUserProfile();

console.log(`Name: ${profile.localizedFirstName} ${profile.localizedLastName}`);
console.log(`ID: ${profile.id}`);
console.log(`Photo: ${profile.profilePicture?.['displayImage~']?.elements?.[0]?.identifiers?.[0]?.identifier}`);

Getting Email Address

Fetch user’s email:

const getUserEmail = async () => {
  const response = await linkedinRequest('/emailAddress?q=members&projection=(emailAddress*)');
  return response;
};

// Usage
const email = await getUserEmail();
console.log(`Email: ${email.elements?.[0]?.emailAddress}`);

Profile Fields Available

Field Permission Description
id r_liteprofile LinkedIn member ID
firstName r_liteprofile First name
lastName r_liteprofile Last name
profilePicture r_liteprofile Profile photo URL
headline r_basicprofile Professional headline
summary r_basicprofile About section
positions r_basicprofile Work history
educations r_basicprofile Education history
emailAddress r_emailaddress Primary email

Content Posting

Creating a Post

Share text post to user’s feed:

const createPost = async (authorUrn, postContent) => {
  const response = await linkedinRequest('/ugcPosts', {
    method: 'POST',
    body: JSON.stringify({
      author: authorUrn,
      lifecycleState: 'PUBLISHED',
      specificContent: {
        'com.linkedin.ugc.ShareContent': {
          shareCommentary: {
            text: postContent.text
          },
          shareMediaCategory: 'NONE'
        }
      },
      visibility: {
        'com.linkedin.ugc.MemberNetworkVisibility': 'PUBLIC'
      }
    })
  });

  return response;
};

// Usage
const post = await createPost('urn:li:person:ABC123', {
  text: 'Excited to announce our new product launch! 🚀 #innovation #startup'
});

console.log(`Post created: ${post.id}`);

Creating a Post with Image

Share post with media:

const createPostWithImage = async (authorUrn, postData) => {
  // Step 1: Register media upload
  const uploadRegistration = await linkedinRequest('/assets?action=registerUpload', {
    method: 'POST',
    body: JSON.stringify({
      registerUploadRequest: {
        recipes: ['urn:li:digitalmediaRecipe:feedshare-image'],
        owner: authorUrn,
        serviceRelationships: [
          {
            relationshipType: 'OWNER',
            identifier: 'urn:li:userGeneratedContent'
          }
        ]
      }
    })
  });

  const uploadUrl = uploadRegistration.value.uploadMechanism['com.linkedin.digitalmedia.uploading.MediaUploadHttpRequest'].uploadUrl;
  const assetUrn = uploadRegistration.value.asset;

  // Step 2: Upload image to provided URL
  await fetch(uploadUrl, {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer ' + await getAccessToken(),
      'Content-Type': 'application/octet-stream'
    },
    body: postData.imageBuffer
  });

  // Step 3: Create post with uploaded image
  const post = await linkedinRequest('/ugcPosts', {
    method: 'POST',
    body: JSON.stringify({
      author: authorUrn,
      lifecycleState: 'PUBLISHED',
      specificContent: {
        'com.linkedin.ugc.ShareContent': {
          shareCommentary: {
            text: postData.text
          },
          shareMediaCategory: 'IMAGE',
          media: [
            {
              status: 'READY',
              description: {
                text: postData.imageDescription || ''
              },
              media: assetUrn,
              title: {
                text: postData.title || ''
              }
            }
          ]
        }
      },
      visibility: {
        'com.linkedin.ugc.MemberNetworkVisibility': 'PUBLIC'
      }
    })
  });

  return post;
};

Creating a Post with Video

Share video content:

const createPostWithVideo = async (authorUrn, postData) => {
  // Register video upload
  const uploadRegistration = await linkedinRequest('/assets?action=registerUpload', {
    method: 'POST',
    body: JSON.stringify({
      registerUploadRequest: {
        recipes: ['urn:li:digitalmediaRecipe:feedshare-video'],
        owner: authorUrn,
        serviceRelationships: [
          {
            relationshipType: 'OWNER',
            identifier: 'urn:li:userGeneratedContent'
          }
        ]
      }
    })
  });

  const assetUrn = uploadRegistration.value.asset;

  // Upload video (use presigned upload URLs from response)
  // ... upload logic ...

  // Create post
  const post = await linkedinRequest('/ugcPosts', {
    method: 'POST',
    body: JSON.stringify({
      author: authorUrn,
      lifecycleState: 'PUBLISHED',
      specificContent: {
        'com.linkedin.ugc.ShareContent': {
          shareCommentary: { text: postData.text },
          shareMediaCategory: 'VIDEO',
          media: [{ status: 'READY', media: assetUrn }]
        }
      },
      visibility: { 'com.linkedin.ugc.MemberNetworkVisibility': 'PUBLIC' }
    })
  });

  return post;
};

Media Specifications

Media Type Format Max Size Duration
Image JPG, PNG, GIF 8MB N/A
Video MP4, MOV 5GB 15 min max
Document PDF, PPT, DOC 100MB N/A

Company Page Management

Getting Company Information

Fetch company page details:

const getCompanyInfo = async (companyId) => {
  const response = await linkedinRequest(
    `/organizations/${companyId}?projection=(id,localizedName,vanityName,tagline,description,universalName,logoV2(original~:playableStreams),companyType,companyPageUrl,confirmedLocations,industries,followerCount,staffCountRange,website, specialties)`
  );
  return response;
};

// Usage
const company = await getCompanyInfo('1234567');
console.log(`Company: ${company.localizedName}`);
console.log(`Followers: ${company.followerCount}`);
console.log(`Website: ${company.website}`);

Posting to Company Page

Share update to company page:

const createCompanyPost = async (organizationUrn, postContent) => {
  const response = await linkedinRequest('/ugcPosts', {
    method: 'POST',
    body: JSON.stringify({
      author: organizationUrn,
      lifecycleState: 'PUBLISHED',
      specificContent: {
        'com.linkedin.ugc.ShareContent': {
          shareCommentary: {
            text: postContent.text
          },
          shareMediaCategory: postContent.media ? 'IMAGE' : 'NONE',
          media: postContent.media ? [
            {
              status: 'READY',
              media: postContent.media.assetUrn
            }
          ] : []
        }
      },
      visibility: {
        'com.linkedin.ugc.MemberNetworkVisibility': 'PUBLIC'
      }
    })
  });

  return response;
};

// Usage
const post = await createCompanyPost('urn:li:organization:1234567', {
  text: 'We're hiring! Join our growing team. #careers #hiring'
});

Getting Company Followers

Fetch follower count:

const getFollowerCount = async (organizationId) => {
  const response = await linkedinRequest(
    `/organizationalEntityFollowerStatistics?q=organizationalEntity&organizationalEntity=urn:li:organization:${organizationId}`
  );
  return response;
};

Rate Limiting

Understanding Rate Limits

LinkedIn enforces rate limits per app:

API Limit Window
Profile API 100 requests Per day
UGC Posts 50 posts Per day
Company Admin 500 requests Per day
Ads API 100 requests Per minute

Rate Limit Headers

Header Description
X-Restli-Quota-Remaining Remaining requests
X-Restli-Quota-Reset Seconds until reset

Troubleshooting Common Issues

Issue: 401 Unauthorized

Solutions:

  1. Verify access token hasn’t expired (60 days)
  2. Check token scope includes requested resource
  3. Ensure Authorization: Bearer {token} header present

Issue: 403 Forbidden

Solutions:

  1. Verify app has required permissions
  2. Check user approved requested scopes
  3. Partner verification may be required

Issue: 429 Rate Limited

Solutions:

  1. Implement request queuing
  2. Cache responses to reduce calls
  3. Use webhooks instead of polling

Production Deployment Checklist

Before going live:

Real-World Use Cases

Recruitment Platform

A recruiting tool automates job postings:

B2B Marketing Automation

A marketing platform schedules LinkedIn content:

Conclusion

The LinkedIn API provides comprehensive access to professional network features. Key takeaways:

button

FAQ Section

How do I get access to LinkedIn API?

Create a LinkedIn Developer account, create an app, and complete Partner verification for advanced API access.

Can I post to LinkedIn automatically?

Yes, use the UGC (User Generated Content) API with w_member_social permission for personal posts or w_organization_social for company posts.

What are LinkedIn rate limits?

Rate limits range from 100-500 requests per day depending on the API. Ads API allows 100 requests per minute.

How long do LinkedIn tokens last?

Access tokens expire after 60 days. Users must re-authenticate to continue API access.

Can I access user connections?

No, LinkedIn removed connections API access for most apps due to privacy changes.

Explore more

How to Use Make (Integromat) API ?

How to Use Make (Integromat) API ?

Master Make (Integromat) API integration with this complete guide. Learn scenario management, webhooks, execution monitoring, and production automation strategies.

25 March 2026

How Do You Build a HIPAA-Compliant API in 2026 for Secure Healthcare Applications?

How Do You Build a HIPAA-Compliant API in 2026 for Secure Healthcare Applications?

Complete guide to HIPAA-compliant API development. Learn PHI handling, encryption, access controls, audit logging, and compliance verification for healthcare applications.

25 March 2026

How to Use AWS Lambda API for Serverless in 2026

How to Use AWS Lambda API for Serverless in 2026

Master AWS Lambda API integration with this complete guide. Learn IAM authentication, function deployment, invocation patterns, event sources, and serverless architecture.

25 March 2026

Practice API Design-first in Apidog

Discover an easier way to build and use APIs