To use the LinkedIn share post API to publish to a personal profile, you need an access token with the w_member_social scope. You make a POST request to the /rest/posts endpoint, passing a LinkedIn-Version header that specifies a recent year and month. The JSON payload requires the author's person URN, which you retrieve from the OpenID userinfo endpoint when the user first authorizes the app.
Which LinkedIn developer product do I need?
LinkedIn has several API products. For publishing to a personal feed from your own code, you want the self-serve product named Share on LinkedIn. You create an app in the LinkedIn Developer portal and add this product to your application settings.
Adding this product grants your app permission to request the w_member_social scope. When you send a user through the OAuth 2.0 flow, you ask for this scope alongside openid, profile, and email. The user sees a consent screen asking them to let your app create posts on their behalf.
How do I get the access token?
Before you can get an access token, the user must approve your app. You direct their browser to https://www.linkedin.com/oauth/v2/authorization. You append query parameters: response_type=code, your client_id, your redirect_uri, and a state parameter to prevent cross-site request forgery.
The scope parameter must be a space-separated list of the permissions you need. For publishing, you request openid profile email w_member_social.
When the user approves the application, LinkedIn redirects to your callback URL with a code query parameter. You make a POST request from your server to https://www.linkedin.com/oauth/v2/accessToken. The form-encoded payload requires grant_type=authorization_code, the code, your redirect_uri, client_id, and client_secret. The response contains your access token.
How long does the access token last?
An access token from the Share on LinkedIn product lasts exactly 60 days.
Self-serve apps do not receive a refresh token. When the 60 days expire, the API will reject your posting attempts with a 401 Unauthorized error. You cannot silently renew this access in the background. The user must click a button in your application, go through the LinkedIn consent screen again, and generate a new token.
If you are building an automated system, you have to plan for this forced re-authorising. You should track the expiry date in your database when you first store the token. This lets you warn the user before the token expires so their scheduled posts do not fail silently.
How do I track token expiration?
Because self-serve apps get no refresh token, you have to monitor that 60-day limit in your code.
When you receive the access token, calculate the exact expiration timestamp. In your database, store the token, the author URN, and this expiration date. Before your application attempts to publish a post, it should check the current date against the expiration date.
If the token is expired, do not attempt the API call. Flag the account in your system as disconnected. I send an email alert asking the user to log in and reconnect their LinkedIn profile. Handling this cleanly prevents your application logs from filling up with errors when background jobs try to process a queue of scheduled posts.
How do I find the author URN?
Every post requires an author identifier. LinkedIn formats this as a Uniform Resource Name, specifically a person URN. It looks like urn:li:person:123456789.
You do not get this URN directly from the OAuth callback. Instead, you use the access token to make a GET request to the OpenID userinfo endpoint.
The endpoint is https://api.linkedin.com/v2/userinfo. You pass the access token in the Authorization header as a Bearer token. The JSON response includes a sub field. This sub string is the unique identifier for the user.
You construct the author URN by appending that sub value to urn:li:person:. Store this string alongside the access token in your database. You will need it every time you construct a post payload.
What headers does the API require?
The LinkedIn API uses versioning based on release months. You must include a LinkedIn-Version header with every request.
The format is YYYYMM, such as 202401 for January 2024. If you omit this header, or provide an invalid date format, the API returns an error. LinkedIn supports these versions for a rolling window of one year. You should periodically update this header in your code to a newer month to ensure you receive the latest API bug fixes.
You also need the standard Authorization: Bearer YOUR_TOKEN header, and a Content-Type: application/json header for the POST payload.
What does the request payload look like?
You send a POST request to https://api.linkedin.com/rest/posts.
The JSON body requires an author field containing the person URN you built earlier. It also needs a commentary field holding the text of your post. The lifecycleState must be set to PUBLISHED.
Here is the exact structure for a plain text post:
{
"author": "urn:li:person:YOUR_ID_HERE",
"commentary": "This is the text of the post.\n\nIt supports line breaks.",
"visibility": "PUBLIC",
"distribution": {
"feedDistribution": "MAIN_FEED",
"targetEntities": [],
"thirdPartyDistributionChannels": []
},
"lifecycleState": "PUBLISHED",
"isReshareDisabledByAuthor": false
}
To format text with paragraphs, you use standard newline characters within the commentary string. LinkedIn renders these as line breaks in the feed.
Can I create a hidden test post?
Developers often try to create invisible posts to verify their code works before pushing live updates. You cannot do this with the standard member post API.
If you attempt to set feedDistribution to NONE, the API rejects the request. LinkedIn reserves dark posts for its advertising products. Member posts created through the API must go straight to the public feed.
To test your integration, you have to publish a real post and then manually delete it from the LinkedIn interface. I recommend keeping your test text short and clear, such as "Testing API integration", so your connections understand what they are seeing before you remove it.
How do I debug API errors?
When your request fails, LinkedIn returns a JSON error response detailing what went wrong.
A 401 status means your token is invalid or expired. A 403 status indicates you lack the w_member_social scope. This often happens if you forgot to add the Share on LinkedIn product to your developer app before generating the token.
A 422 Unprocessable Entity error usually means your JSON structure is wrong or you provided an invalid URN format. The error message inside the payload will specify which field failed validation. If you encounter a 422 error complaining about distribution settings, check that you did not accidentally set feedDistribution to NONE.
How do I handle links and link previews?
If your post includes a URL, LinkedIn automatically detects it in the text and creates a preview card at the bottom of the post. You do not need to submit the URL as a separate attachment object if you want the default preview scraped from your web page. Just ensure the URL is complete, starting with https://.
When you include a link, LinkedIn crawls that URL for Open Graph tags. If your application publishes a blog post and immediately triggers a LinkedIn API call, the crawler might hit your site before your caching layer has served the new page. This results in a broken preview card. To avoid this, introduce a short delay between publishing the article and hitting the LinkedIn API.
How does this compare to automating other platforms?
Writing good text for that commentary field matters. The platform truncates long text, forcing users to click to read the rest. I covered the math for this in my guide on the LinkedIn post character limit before see more.
When you automate publishing from a blog, you also have to convert HTML into plain text. You must strip all HTML tags from your content before sending it. If you leave tags in the text string, LinkedIn prints the literal bracket characters to the feed.
If you are pushing to multiple networks at once, you need to track failures independently per network. This prevents duplicate posts on one platform when another platform's API times out. I documented my approach to this in a piece on handling partial syndication failures without creating duplicate posts.
Other platforms handle authentication and payloads differently. For example, Bluesky requires fetching a DID and passing an app password, and the access tokens refresh automatically. You can see how that structure differs in my guide on how to use the Bluesky API to create a post.
I built AmplifySignal to automate this entirely, connecting your profile once to draft and schedule posts directly to LinkedIn the morning after your article goes live.