LinkedIn is the world's largest professional platform, used for networking, recruiting, sharing content and career development. It offers a feature-rich API that enables you to build integrations for LinkedIn user authentication and verification, content publishing, ads, events, and community management.
However, as a new user, the LinkedIn API can be difficult to work with because it spans multiple products, and access often requires approvals and permission scopes.
Therefore, in this guide, you will learn how to use and integrate the LinkedIn API into your software applications and automation workflows, from user authentication to publishing content. The goal is to turn hours of trial and error into a clear, repeatable process you can apply in your own applications.
How to Access the LinkedIn Developer Portal
The LinkedIn Developer Portal is where you create and manage applications that can securely access LinkedIn APIs, enabling you to configure authentication, request permissions, and manage access to LinkedIn resources.
To get started, you must create a LinkedIn page that will be linked with the application.
Then, navigate to the LinkedIn Developer Portal to create the application.
Provide your App name, upload the logo, and select your newly created page from the drop-down list to create the app.
Next, verify the LinkedIn page to ensure you authorised linking it with the app.
It will display a dialogue box that asks you to generate the verification URL. Generate it and open the link in your browser to verify the page.
After successfully creating the application, a client ID and primary client secret have been issued for your app's authentication and authorisation. Copy and save them for later.
Finally, click the Products tab and request access to user authentication and sharing of posts on LinkedIn.
Congratulations! You can start authenticating users and creating posts via the LinkedIn API.
How LinkedIn API Authentication and Authorisation Works
The LinkedIn API uses OAuth 2.0 to authorise applications and grant secure access to LinkedIn resources. Before your application can request data or act on behalf of a user, it must complete an OAuth authorisation flow.
LinkedIn supports two authorisation methods:
- Member Authorisation (3-legged OAuth): This is the most common flow that grants your application permission to access users' LinkedIn data and perform actions on their behalf.
- Application Authorisation (2-legged OAuth): This flow is used when your application does not need access to a specific user's data. Only a few LinkedIn APIs support this method.
In this section, you will implement the 3-legged OAuth flow, which involves requesting an authorisation code from LinkedIn, exchanging it for an access token, and using that token to access member data.
Before we proceed, add the following redirect URL to the list of Redirect URLs in your LinkedIn developer dashboard. LinkedIn will send the authorisation code to this URL during this development phase.
http://localhost:3000/auth/linkedin/callback
Node.js Prerequisites
The examples in this guide were written in JavaScript. Therefore, install the following tools to ensure you can execute the code on your computer:
-
Node.js - a JavaScript runtime used to run the example code locally. You can download it from the official Node.js website and verify your installation by running
node -vin your terminal. - Code editor - any modern code editor will work. This tutorial uses Visual Studio Code, but you can use your preferred editor.
Implementing LinkedIn User Authentication and Authorisation in Node.js
Create a new folder for the project and install a package.json file using the code snippet below:
mkdir linkedin-api-guide
cd linkedin-api-guide
npm init -y
Run the following code snippet in the terminal to install the project dependencies:
npm install axios dotenv express
Add an index.js and .env files to the project directory.
cd linkedin-api-guide
touch index.js .env
Declare the following variables within the .env file and fill it with your credentials.
ACCESS_TOKEN=
CLIENT_ID=<your_LinkedIn_client_ID>
CLIENT_SECRET=<your_LinkedIn_client_secret>
URI=http://localhost:3000/auth/linkedin/callback
Now let's implement the member authorisation (3-legged) workflow.
Copy the following code snippet into the index.js file to import the packages and environment variables.
// Package imports
require("dotenv").config();
const express = require("express");
const axios = require("axios");
const app = express();
// Environment variables
const CLIENT_ID = process.env.CLIENT_ID;
const CLIENT_SECRET = process.env.CLIENT_SECRET;
const REDIRECT_URI = process.env.URI;
The dotenv package loads environment variables from a .env file into your application, keeping sensitive credentials out of your source code. Express provides a lightweight web server for handling OAuth redirects and API routes, while Axios is used to send HTTP requests to LinkedIn's API endpoints.
Add the following code snippet into the index.js file:
app.get("/auth/linkedin", (req, res) => {
const scope = "openid profile email w_member_social"; // Modern OIDC scopes
const state = "foobar"; // Use for CSRF protection
const callbackUrl = `https://www.linkedin.com/oauth/v2/authorization?response_type=code&client_id=${CLIENT_ID}&redirect_uri=${encodeURIComponent(REDIRECT_URI)}&state=${state}&scope=${encodeURIComponent(scope)}`;
res.redirect(callbackUrl);
});
The code above defines the LinkedIn OAuth authorisation URL, required permission scopes and a state parameter for CSRF protection, then redirects the user to LinkedIn's authorisation page. If the user approves access, LinkedIn redirects them back to your callback URL with an authorisation code.
Next, declare another API route that fetches the user's access token using the auth code.
app.get("/auth/linkedin/callback", async (req, res) => {
// 1. Get the code from the URL query parameters
const code = req.query.code;
if (!code) {
return res.status(400).send("Authorization failed: No code provided.");
}
try {
// 2. Make the POST request to exchange the code for an Access Token
// LinkedIn requires 'application/x-www-form-urlencoded'
const response = await axios.post(
"https://www.linkedin.com/oauth/v2/accessToken",
null,
{
params: {
grant_type: "authorization_code",
code: code,
client_id: CLIENT_ID,
client_secret: CLIENT_SECRET,
redirect_uri: REDIRECT_URI,
},
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
},
);
const accessToken = response.data.access_token;
console.log("Access Token:", accessToken);
// 3. (Optional) Immediately use the token to get the user's name/email
const userProfile = await axios.get(
"https://api.linkedin.com/v2/userinfo",
{
headers: { Authorization: `Bearer ${accessToken}` },
},
);
res.json({
message: "Success!",
token: accessToken,
profile: userProfile.data,
});
} catch (error) {
console.error(
"Error exchanging code:",
error.response?.data || error.message,
);
res.status(500).json(error.response?.data || "Internal Server Error");
}
});
The authorisation code from LinkedIn is exchanged for an access token, which your application uses to access the user's data.
Finally, add the following code snippet to the file to start the server and run it using node index.js:
// Start the server
app.listen(3000, () =>
console.log("Server: http://localhost:3000/auth/linkedin"),
);
Here is a sample of the data returned from the authorisation API:
{
"message": "Success!",
"token": "AQXs_b_gXF9pXBdwdvmOWEL4TQXKiHWAjnRvP-WNe4nxaIq",
"profile": {
"sub": "xzorpyHada",
"email_verified": true,
"name": "Jane Doe",
"locale": {
"country": "US",
"language": "en"
},
"given_name": "Jane",
"family_name": "Doe",
"email": "janedoe@gmail.com",
"picture": "https://media.licdn.com/dms/image/v2/<image_id>"
}
}
Note: The code example logs the access token to the console. In a real application, you should store the token securely (for example, in a database or a secure environment variable) rather than saving it directly in your
.envfile.
How to Post Content using the LinkedIn API
In this section, you will learn how to post content, including text-only posts, articles, images, and videos on LinkedIn via its API. We'll build on the index.js file from the previous section to add endpoints for creating these posts.
Ensure you have added your access token to its environment variable in the
.envfile.
Text Only Posts
Add the following code snippet to the index.js file:
app.get("/auth/linkedin/post/share", async (req, res) => {
const accessToken = process.env.ACCESS_TOKEN;
message = "Hello LinkedIn!";
if (!accessToken) {
return res.status(401).send("Access token required");
}
try {
// STEP 1: Get the Member's ID (Person URN)
const userResponse = await axios.get(
"https://api.linkedin.com/v2/userinfo",
{
headers: { Authorization: `Bearer ${accessToken}` },
},
);
const personURN = `urn:li:person:${userResponse.data.sub}`;
// STEP 2: Make the POST request to share
const shareResponse = await axios.post(
"https://api.linkedin.com/v2/ugcPosts",
{
author: personURN,
lifecycleState: "PUBLISHED",
specificContent: {
"com.linkedin.ugc.ShareContent": {
shareCommentary: {
text: message || "Default share text from Node.js!",
},
shareMediaCategory: "NONE",
},
},
visibility: {
"com.linkedin.ugc.MemberNetworkVisibility": "PUBLIC",
},
},
{
headers: {
Authorization: `Bearer ${accessToken}`,
"X-Restli-Protocol-Version": "2.0.0",
"Content-Type": "application/json",
},
},
);
res.status(201).json({
message: "Post shared successfully!",
data: shareResponse.data,
});
} catch (error) {
console.error(
"Error sharing content:",
error.response?.data || error.message,
);
res
.status(error.response?.status || 500)
.json(error.response?.data || "Internal Server Error");
}
});
From the code snippet above, the route first retrieves the member's LinkedIn ID (URN) using the access token, which is required as the author field when creating a post.
It then sends a POST request to LinkedIn's ugcPosts endpoint with the post content, lifecycle state, media category, and visibility settings. If successful, the API returns the created post's data; otherwise, any errors are logged and returned in the response.
Test the endpoint by pointing your Express server to the /auth/linkedin/post/share route.
If successful, it returns the following JSON object:
{
"message": "Post shared successfully!",
"data": {
"id": "urn:li:share:<id>"
}
}
Articles or URL Posts
To share articles or URLs using the LinkedIn API, update the specificContent object by adding a media array and setting shareMediaCategory to "ARTICLE". This tells LinkedIn that the post contains a link or article rather than just text.
Add a new API endpoint for creating articles or URL posts.
app.get("/auth/linkedin/article/share", async (req, res) => {
const accessToken = process.env.ACCESS_TOKEN;
message = "Hello LinkedIn!";
if (!accessToken) {
return res.status(401).send("Access token required");
}
try {
const userResponse = await axios.get(
"https://api.linkedin.com/v2/userinfo",
{
headers: { Authorization: `Bearer ${accessToken}` },
},
);
const personURN = `urn:li:person:${userResponse.data.sub}`;
// Make the POST request to share
const shareResponse = await axios.post(
"https://api.linkedin.com/v2/ugcPosts",
{
author: personURN,
lifecycleState: "PUBLISHED",
specificContent: {
"com.linkedin.ugc.ShareContent": {
shareCommentary: {
text: message || "Default share text from Node.js!",
},
shareMediaCategory: "ARTICLE",
media: [
{
status: "READY",
description: {
text: "Official LinkedIn Blog - Your source for insights and information about LinkedIn.",
},
originalUrl: "https://blog.linkedin.com/",
title: {
text: "Official LinkedIn Blog",
},
},
],
},
},
visibility: {
"com.linkedin.ugc.MemberNetworkVisibility": "PUBLIC",
},
},
{
headers: {
Authorization: `Bearer ${accessToken}`,
"X-Restli-Protocol-Version": "2.0.0",
"Content-Type": "application/json",
},
},
);
res.status(201).json({
message: "Post shared successfully!",
data: shareResponse.data,
});
} catch (error) {
console.error(
"Error sharing content:",
error.response?.data || error.message,
);
res
.status(error.response?.status || 500)
.json(error.response?.data || "Internal Server Error");
}
});
The route retrieves the member's LinkedIn ID (URN), sets shareMediaCategory to "ARTICLE", and includes a media array containing the article's URL, title, and description. The shareCommentary.text field provides the post message that appears above the article preview.
Image or Video Upload
To upload images or videos using the LinkedIn API, you need to register the image or video, upload it to LinkedIn, and create an image or video share.
First, create the register function as shown below:
async function registerMedia(accessToken, personURN, mediaType = "image") {
const registerUrl =
"https://api.linkedin.com/v2/assets?action=registerUpload";
// Use 'feedshare-image' for images and 'feedshare-video' for videos
const recipe =
mediaType === "video"
? "urn:li:digitalmediaRecipe:feedshare-video"
: "urn:li:digitalmediaRecipe:feedshare-image";
const body = {
registerUploadRequest: {
recipes: [recipe],
owner: personURN,
serviceRelationships: [
{
relationshipType: "OWNER",
identifier: "urn:li:userGeneratedContent",
},
],
},
};
const response = await axios.post(registerUrl, body, {
headers: {
Authorization: `Bearer ${accessToken}`,
"X-Restli-Protocol-Version": "2.0.0",
"Content-Type": "application/json",
},
});
return {
uploadUrl:
response.data.value.uploadMechanism[
"com.linkedin.digitalmedia.uploading.MediaUploadHttpRequest"
].uploadUrl,
asset: response.data.value.asset,
};
}
The registerMedia function accepts an image or video upload, the user's access token and the person's URN. The mediaType parameter determines whether the asset is an image (feedshare-image) or a video (feedshare-video).
Next, add the upload function:
async function uploadBinary(uploadUrl, accessToken, filePath) {
const fileBuffer = fs.readFileSync(filePath);
await axios.put(uploadUrl, fileBuffer, {
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/octet-stream",
"X-Restli-Protocol-Version": "2.0.0",
},
});
console.log("Upload successful.");
}
The uploadBinary function uploads the media file (image or video) to LinkedIn using the uploadUrl returned by the registerMedia function.
Finally, create the share function that utilises both functions before sharing the image/video to LinkedIn.
app.get("/auth/linkedin/share-media", async (req, res) => {
const accessToken = process.env.ACCESS_TOKEN;
const filePath = "./cover.png"; // Path to your local file
try {
// Step 0: Get User URN
const userRes = await axios.get("https://api.linkedin.com/v2/userinfo", {
headers: { Authorization: `Bearer ${accessToken}` },
});
const personURN = `urn:li:person:${userRes.data.sub}`;
// Step 1: Register
const { uploadUrl, asset } = await registerMedia(
accessToken,
personURN,
"image",
);
// Step 2: Upload Binary
await uploadBinary(uploadUrl, accessToken, filePath);
// Step 3: Create the UGC Post using the asset
const shareResponse = await axios.post(
"https://api.linkedin.com/v2/ugcPosts",
{
author: personURN,
lifecycleState: "PUBLISHED",
specificContent: {
"com.linkedin.ugc.ShareContent": {
shareCommentary: { text: "Posting with an image!" },
shareMediaCategory: "IMAGE",
media: [
{
status: "READY",
media: asset, // The URN from Step 1
title: { text: "My Image Title" },
},
],
},
},
visibility: { "com.linkedin.ugc.MemberNetworkVisibility": "PUBLIC" },
},
{
headers: {
Authorization: `Bearer ${accessToken}`,
"X-Restli-Protocol-Version": "2.0.0",
},
},
);
res.json({ success: true, post: shareResponse.data });
} catch (error) {
console.error(error.response?.data || error.message);
res.status(500).json(error.response?.data || "Upload failed");
}
});
The code above demonstrates the full workflow for posting an image to LinkedIn:
- It first retrieves the user's LinkedIn ID (URN) using the access token.
- The image is then registered with LinkedIn via the
registerMediafunction, returning anuploadUrlandassetidentifier. - The
uploadBinaryfunction uploads the image file to LinkedIn using the provideduploadUrl. - Finally, a UGC post is created using the asset ID, setting the
shareMediaCategoryto"IMAGE"and including a post message and optional title.
The examples in this guide use HTTP GET requests to allow testing the API endpoints directly in a web browser. In a real-world application, you should replace these GET requests with POST requests (or the appropriate HTTP method) where required by LinkedIn's API.
Recommended: Unified Solution for LinkedIn Posts
Late is an all-in-one social media scheduling platform that allows you to connect multiple social media accounts and publish posts across them. With its API, you can schedule and publish social media content, including images or videos, across 13 platforms, including LinkedIn.
LinkedIn for Outreach: API vs Automation
So far, you've learnt how to create and share content on LinkedIn using its API. However, the API has limitations: it doesn't support direct messages or connection requests, and using browser automation tools like Selenium or Puppeteer can put your account at risk.
This is where WaLead comes in. WaLead is a cloud-based LinkedIn automation platform for connection requests, personalised messaging, and campaign management at scale. It doesn't require a browser, supports multiple accounts, and eliminates account risk.
With WaLead, sales and marketing teams can:
- Extract leads from LinkedIn
- Screen and connect with potential customers
- Import leads from CSV or Google Sheets
All of this can be done easily and safely, without jeopardising your LinkedIn account.
LinkedIn API vs Late API vs WaLead
| What you need | LinkedIn API | Late API | WaLead |
|---|---|---|---|
| Post to LinkedIn | OAuth + media upload | One API call | N/A |
| Post to 13 platforms | Build each integration | One API call | N/A |
| LinkedIn outreach/DMs | Not available via API | N/A | Full automation |
| Token management | Build refresh logic | Automatic | Automatic |
| Scale many accounts | Complex multi-tenant OAuth | Multi-profile support | Unlimited accounts |
Conclusion
In this guide, you've learned how to create and share content using the LinkedIn API. For easier authentication and more advanced features, the Late API provides a powerful solution that also allows you to schedule and publish posts, including media, across 13 social media platforms, including LinkedIn.
While these APIs are great for content publishing, they don't cover direct messages, connection requests, or large-scale outreach, areas where WaLead excels. Together, these tools give developers, marketers, and sales teams a complete toolkit for content creation, scheduling, and outreach on LinkedIn.
Thank you for reading!








Top comments (0)