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.
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.
.png)
Then, navigate to the LinkedIn Developer Portal to create the application.
%20(1).png)
Provide your App name, upload the logo, and select your newly created page from the drop-down list to create the app.
.png)
Next, verify the LinkedIn page to ensure you authorised linking it with the app.
.png)
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.
.png)
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.
.png)
Finally, click the Products tab and request access to user authentication and sharing of posts on LinkedIn.
.png)
Congratulations! You can start authenticating users and creating posts via the LinkedIn API.
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:
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
.png)
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 -v in your terminal.
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;
//👇🏻 log access token to the console
console.log("Access Token:", accessToken);
/*
3-legged authorisation method placeholder
👇🏻 3. (Optional) Immediately use the token to get the user's name/email
*/
} catch (error) {
console.error(
"Error exchanging code:",
error.response?.data || error.message,
);
res.status(500).json(error.response?.data || "Internal Server Error");
}
});
The code above handles the callback from LinkedIn after the user authorises your application. It retrieves the authorisation code from the query parameters and exchanges it for an access token using your client ID, client secret, and redirect URI. The access token can then be used to make authorised API requests on behalf of the user.
To fetch the user's profile using the access token, update the code snippet as shown below:
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 .env file.
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 .env file.
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)
// We need this because the 'author' field requires the URN format
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", // or "CONNECTIONS"
},
},
{
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,
);
// Return the specific LinkedIn error to help debugging
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.
app.listen(3000, () =>
console.log("Server: http://localhost:3000/auth/linkedin/post/share"),
);
If successful, it returns the following JSON object in your browser:
{
"message": "Post shared successfully!",
"data": {
"id": "urn:li:share:<id>"
}
}
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}`;
// 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: "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", // or "CONNECTIONS"
},
},
{
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,
);
// Return the specific LinkedIn error to help debugging
res
.status(error.response?.status || 500)
.json(error.response?.data || "Internal Server Error");
}
});
From the code snippet above,
author field when creating a post.shareMediaCategory to "ARTICLE" and includes a media array containing the article’s URL, title, and description.shareCommentary.text field provides the post message that appears above the article preview.ugcPosts endpoint, creating a published article post on the user’s feed.Test the endpoint by pointing your Express server to the /auth/linkedin/article/share route.
app.listen(3000, () =>
console.log("Server: http://localhost:3000/auth/linkedin/post/share"),
);
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,
};
}
From the code snippet above,
feedshare-image) or a video (feedshare-video).assets?action=registerUpload endpoint with the required recipe and ownership information, which returns the uploadUrl and the asset identifier.Next, add the upload function to the index.js file.
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", // or the specific mime-type (image/png, video/mp4)
"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 the registerMedia and uploadBinary 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 (if you don't have it)
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", // Change to VIDEO if uploading video
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 snippet above demonstrates the full workflow for posting an image to LinkedIn:
registerMedia function, returning an uploadUrl and asset identifier.uploadBinary function uploads the image file to LinkedIn using the provided uploadUrl.shareMediaCategory to "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.
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.
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:
All of this can be done easily and safely, without jeopardising your LinkedIn account.
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!
.png)
.png)
Yes. To use many LinkedIn API features, you must create an application in the LinkedIn Developer Portal and request access to specific products such as user authentication or content sharing. Some features require LinkedIn approval before they become available in your app.
No. The LinkedIn API does not allow developers to send direct messages or connection requests. It mainly supports features such as authentication, profile access, and content publishing.
The LinkedIn API uses OAuth 2.0 for authentication and authorization. Most integrations rely on the 3-legged OAuth flow, where users grant permission to your application before it can access their data.
Yes. However, posting media requires a multi-step process: registering the media upload, uploading the file to LinkedIn, and then creating a post that references the uploaded asset.

How to build effective LinkedIn outreach sequences that boost replies and drive more qualified leads

Import leads from CSV or Sheets into WaLead and run scalable, automated LinkedIn outreach campaigns.
Get step-by-step tips for using activity timelines and status updates on LinkedIn
.png)